This is an automated email from the git hooks/post-receive script.
pierov pushed a change to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
from c801d6d34aec9 Bug 1684739: Only check if swipe tracking is enabled on macOS if the event is a scrollwheel event. r=mstange, a=RyanVM new c328863e77621 Bug 1770137 - Make notification code use Object.create. r=Gijs,freddyb,a=dsmith new 189cc425a3c8d Bug 1770137 - Part 2, r=Gijs, a=dsmith new b3fc1153364b2 Bug 1770048: Improve self-hosted new_List (ESR) r=jandem,tcampbell, a=dsmith new 514d00275d21a Fixed a Firefox include problem new bd3d62631fad7 Revert "Bug 1724777, optimize suppressed MicroTask handling, r=mccr8 a=RyanVM" new 03abec7978bec Adding issue template for bugs. new 62b5438836fca Base Browser's .mozconfigs. new 8744faeafd6a4 Bug 24796 - Comment out excess permissions from GeckoView new 95b11cbd24566 Bug 25741 - TBA: Disable GeckoNetworkManager new 0069499a6abb5 Bug 28125 - Prevent non-Necko network connections new 43b312a25a3c5 Bug 12974: Disable NTLM and Negotiate HTTP Auth new 5eb63a4a77f4d Bug 40166: Disable security.certerrors.mitm.auto_enable_enterprise_roots new 08badffdb0ef4 Bug 16285: Exclude ClearKey system for now new 05030ce98b979 Bug 21431: Clean-up system extensions shipped in Firefox new 55c1bd7df21fb Bug 33852: Clean up about:logins (LockWise) to avoid mentioning sync, etc. new 3d165743e9931 Bug 40025: Remove Mozilla add-on install permissions new 444644b403b58 Bug 40002: Remove about:ion new 7160d9dfe275c Bug 18821: Disable libmdns for Android and Desktop new 5eac23294daf4 Bug 26353: Prevent speculative connect that violated FPI. new 7ecef3c2e4e01 Bug 31740: Remove some unnecessary RemoteSettings instances new 5f704c50b8cc0 Bug 30541: Disable WebGL readPixel() for web content new 3150d641887f8 Bug 28369: Stop shipping pingsender executable new 568fe5cb0bb7d Bug 40073: Disable remote Public Suffix List fetching new ad8c976086edf Firefox preference overrides. new 4a967032d1011 Bug 40125: Expose Security Level pref in GeckoView new 33199ea462fb0 Bug 30605: Honor privacy.spoof_english in Android new 8a60de0ee4edc Bug 40199: Avoid using system locale for intl.accept_languages in GeckoView new fe47e4626b8df Bug 40198: Expose privacy.spoof_english pref in GeckoView new e1ed71fbd2e90 Bug 40171: Make WebRequest and GeckoWebExecutor First-Party aware new 9d6721506b5e7 Bug 26345: Hide tracking protection UI new 21cf79937a4d6 Bug 9173: Change the default Firefox profile directory to be TBB-relative. new 6a622ebd6cd39 Bug 18800: Remove localhost DNS lookup in nsProfileLock.cpp new dd9c91a922883 Bug 27604: Fix addon issues when moving the profile directory new ee630bac7d57b Bug 32418: Add a configure flag to load policies only from the local policies.json new f4639ba025e48 Bug 13028: Prevent potential proxy bypass cases. new 970e7f414191e Bug 11641: Disable remoting by default. new 19e8ff3fe5256 Bug 21830: Copying large text from web console leaks to /tmp new aa4581a0fa54c Bug 23104: Add a default line height compensation new 3f28b38a3ffb6 Bug 40309: Avoid using regional OS locales new 7794e1b067b70 Bug 40432: Prevent probing installed applications new 918405506e829 Bug 32220: Improve the letterboxing experience new 5e38e274a262b Bug 2176: Rebrand Firefox to TorBrowser new 20cbc7a61c8c5 Bring back old Firefox onboarding new 2b5033f50d7bf Bug 26961: New user onboarding. new c197e8caaac0a Bug 40069: Add helpers for message passing with extensions new 9c69aa96a150f TB3: Tor Browser's official .mozconfigs. new 4fd687e014228 Bug 40562: Added Tor-related preferences to 000-tor-browser.js new 690774f96eee3 squash! Bug 40562: Added Tor-related preferences to 000-tor-browser.js new ff80e1436cfe4 Bug 13252: Do not store data in the app bundle new d4fbdbc386c53 Bug 40597: Implement TorSettings module new 91ad24bac8bf8 fixup! Bug 40597: Implement TorSettings module new a001d0e89b94f fixup! Bug 40597: Implement TorSettings module new e857e60c1f857 Bug 10760: Integrate TorButton to TorBrowser core new eb8057e5d87ad Bug 28044: Integrate Tor Launcher into tor-browser new 0df3bae087129 Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources. new d87f1aeae255d Add TorStrings module for localization new b87b6a504ab80 fixup! Add TorStrings module for localization new 380e9bb0fae3d fixup! Add TorStrings module for localization new ea7c8d9d6a1f3 Bug 14631: Improve profile access error messages. new 0bee2cf9ef8d2 40209: Implement Basic Crypto Safety new 8c2d347aba89b Bug 19273: Avoid JavaScript patching of the external app helper dialog. new f3287cf33d4ab Bug 40807: Added QRCode.js to toolkit/modules new 4a4cd91e58664 Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection new 6bb230b225baf fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection new 1aca00cf41dfb fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection new ae25e8f09899f Bug 27476: Implement about:torconnect captive portal within Tor Browser new d3f26be63d210 fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser new fbff9f39c5664 fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser new efff98ff20dba Bug 12620: TorBrowser regression tests new 333f9f41a2d1c Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop new 565b9ab2e6f47 Bug 40253: Explicitly allow NoScript in Private Browsing mode. new ee931063ef7aa Bug 25658: Replace security slider with security level UI new b304b5f34b393 Bug 27511: Add new identity button to toolbar new 57f2c989bc5c7 Bug 4234: Use the Firefox Update Process for Tor Browser. new 1414610f0ef0e Bug 13379: Sign our MAR files. new 03e2234b71de7 Bug 16940: After update, load local change notes. new fa00b9ff173d2 Bug 32658: Create a new MAR signing key new e1f63c6564890 Omnibox: Add DDG, Startpage, Disconnect, Youtube, Twitter; remove Amazon, eBay, bing new f406290d35ea9 Bug 23247: Communicating security expectations for .onion new c76ed4df6694f Bug 30237: Add v3 onion services client authentication prompt new 0edd5ddc3a36b Bug 28005: Implement .onion alias urlbar rewrites new 22c25b9bad7f6 Bug 21952: Implement Onion-Location new 4c6c26f9a02a8 Bug 40458: Implement .tor.onion aliases new 2ccfccba93535 Bug 11698: Incorporate Tor Browser Manual pages into Tor Browser new e10b30227eb46 fixup! Base Browser's .mozconfigs.
The 85 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: .eslintignore | 3 + .gitlab/issue_templates/UXBug.md | 29 + .gitlab/issue_templates/bug.md | 32 + .gitmodules | 3 + browser/actors/AboutTBUpdateChild.jsm | 12 + browser/actors/AboutTBUpdateParent.jsm | 120 ++ browser/actors/CryptoSafetyChild.jsm | 87 + browser/actors/CryptoSafetyParent.jsm | 142 ++ browser/actors/NetErrorChild.jsm | 7 + browser/actors/NetErrorParent.jsm | 8 + browser/actors/moz.build | 8 + browser/app/Makefile.in | 4 +- browser/app/macbuild/Contents/Info.plist.in | 2 +- browser/app/macbuild/Contents/MacOS-files.in | 1 - browser/app/permissions | 15 +- browser/app/profile/000-tor-browser.js | 93 + browser/app/profile/001-base-profile.js | 592 ++++++ browser/app/profile/firefox.js | 32 +- browser/base/content/aboutDialog-appUpdater.js | 2 +- browser/base/content/aboutDialog.js | 12 +- browser/base/content/aboutDialog.xhtml | 38 +- .../base/content/abouttbupdate/aboutTBUpdate.css | 74 + .../base/content/abouttbupdate/aboutTBUpdate.js | 27 + .../base/content/abouttbupdate/aboutTBUpdate.xhtml | 39 + browser/base/content/appmenu-viewcache.inc.xhtml | 32 +- browser/base/content/browser-doctype.inc | 6 + browser/base/content/browser-menubar.inc | 45 +- browser/base/content/browser-sets.inc | 2 + browser/base/content/browser-siteIdentity.js | 47 +- browser/base/content/browser.css | 7 + browser/base/content/browser.js | 103 +- browser/base/content/browser.xhtml | 14 + browser/base/content/certerror/aboutNetError.js | 22 +- browser/base/content/certerror/aboutNetError.xhtml | 1 + browser/base/content/main-popupset.inc.xhtml | 3 + browser/base/content/navigator-toolbox.inc.xhtml | 17 + browser/base/content/pageinfo/security.js | 64 +- browser/base/content/popup-notifications.inc | 14 + browser/base/content/tab-content.js | 6 + browser/base/content/tabbrowser-tab.js | 9 + browser/base/content/utilityOverlay.js | 14 + browser/base/jar.mn | 5 + browser/base/moz.build | 3 + browser/branding/alpha/VisualElements_150.png | Bin 0 -> 8412 bytes browser/branding/alpha/VisualElements_70.png | Bin 0 -> 3496 bytes browser/branding/alpha/background.png | Bin 0 -> 33362 bytes browser/branding/alpha/bgstub.jpg | Bin 0 -> 12506 bytes browser/branding/alpha/bgstub_2x.jpg | Bin 0 -> 49771 bytes browser/branding/{nightly => alpha}/branding.nsi | 0 .../branding/alpha/configure.sh | 8 +- browser/branding/alpha/content/about-logo.png | Bin 0 -> 21173 bytes .../{nightly => alpha}/content/about-logo.svg | 0 browser/branding/alpha/content/about-logo@2x.png | Bin 0 -> 51309 bytes browser/branding/alpha/content/about-wordmark.svg | 36 + browser/branding/alpha/content/about.png | Bin 0 -> 18520 bytes .../{nightly => alpha}/content/aboutDialog.css | 0 browser/branding/alpha/content/aboutlogins.svg | 59 + .../content/firefox-wordmark.svg | 0 .../alpha/content/identity-icons-brand.svg | 8 + .../branding/{official => alpha}/content/jar.mn | 4 + .../branding/{aurora => alpha}/content/moz.build | 0 browser/branding/alpha/content/tor-styles.css | 13 + browser/branding/alpha/default128.png | Bin 0 -> 9397 bytes browser/branding/alpha/default16.png | Bin 0 -> 811 bytes browser/branding/alpha/default22.png | Bin 0 -> 1240 bytes browser/branding/alpha/default24.png | Bin 0 -> 1368 bytes browser/branding/alpha/default256.png | Bin 0 -> 20481 bytes browser/branding/alpha/default32.png | Bin 0 -> 1956 bytes browser/branding/alpha/default48.png | Bin 0 -> 3067 bytes browser/branding/alpha/default512.png | Bin 0 -> 44907 bytes browser/branding/alpha/default64.png | Bin 0 -> 4318 bytes browser/branding/alpha/disk.icns | Bin 0 -> 1548786 bytes browser/branding/alpha/document.icns | Bin 0 -> 564054 bytes browser/branding/alpha/document.ico | Bin 0 -> 119671 bytes browser/branding/{nightly => alpha}/dsstore | Bin .../firefox.VisualElementsManifest.xml | 2 +- browser/branding/alpha/firefox.icns | Bin 0 -> 291096 bytes browser/branding/alpha/firefox.ico | Bin 0 -> 119941 bytes browser/branding/alpha/firefox.svg | 25 + browser/branding/alpha/firefox64.ico | Bin 0 -> 119941 bytes .../{official => alpha}/locales/en-US/brand.dtd | 8 +- .../{nightly => alpha}/locales/en-US/brand.ftl | 2 +- .../locales/en-US/brand.properties | 12 +- .../branding/{official => alpha}/locales/jar.mn | 0 .../{aurora/content => alpha/locales}/moz.build | 0 browser/branding/{aurora => alpha}/moz.build | 0 browser/branding/{aurora => alpha}/newtab.ico | Bin browser/branding/{aurora => alpha}/newwindow.ico | Bin browser/branding/{aurora => alpha}/pbmode.ico | Bin browser/branding/alpha/pref/firefox-branding.js | 34 + .../{nightly => alpha}/stubinstaller/bgstub.jpg | Bin .../stubinstaller/installing_page.css | 0 .../stubinstaller/profile_cleanup_page.css | 0 browser/branding/alpha/wizHeader.bmp | Bin 0 -> 34254 bytes browser/branding/alpha/wizHeaderRTL.bmp | Bin 0 -> 34254 bytes browser/branding/alpha/wizWatermark.bmp | Bin 0 -> 206038 bytes browser/branding/branding-common.mozbuild | 2 + browser/branding/nightly/VisualElements_150.png | Bin 25470 -> 11666 bytes browser/branding/nightly/VisualElements_70.png | Bin 9590 -> 4273 bytes browser/branding/nightly/configure.sh | 8 +- .../nightly/content/identity-icons-brand.svg | 8 + browser/branding/nightly/content/jar.mn | 4 + browser/branding/nightly/content/tor-styles.css | 13 + browser/branding/nightly/default128.png | Bin 12392 -> 13686 bytes browser/branding/nightly/default16.png | Bin 756 -> 891 bytes browser/branding/nightly/default22.png | Bin 1146 -> 1377 bytes browser/branding/nightly/default24.png | Bin 1281 -> 1509 bytes browser/branding/nightly/default256.png | Bin 30546 -> 33587 bytes browser/branding/nightly/default32.png | Bin 1910 -> 2254 bytes browser/branding/nightly/default48.png | Bin 3606 -> 3789 bytes browser/branding/nightly/default512.png | Bin 0 -> 87830 bytes browser/branding/nightly/default64.png | Bin 4826 -> 5426 bytes browser/branding/nightly/document.icns | Bin 517716 -> 689723 bytes browser/branding/nightly/document.ico | Bin 47042 -> 124422 bytes .../nightly/firefox.VisualElementsManifest.xml | 2 +- browser/branding/nightly/firefox.icns | Bin 1014680 -> 642308 bytes browser/branding/nightly/firefox.ico | Bin 66730 -> 131711 bytes browser/branding/nightly/firefox.svg | 29 + browser/branding/nightly/firefox64.ico | Bin 38630 -> 131711 bytes browser/branding/nightly/locales/en-US/brand.dtd | 8 +- browser/branding/nightly/locales/en-US/brand.ftl | 2 +- .../nightly/locales/en-US/brand.properties | 10 +- browser/branding/nightly/locales/jar.mn | 7 +- browser/branding/nightly/locales/moz.build | 2 - browser/branding/nightly/wizHeader.bmp | Bin 25820 -> 34254 bytes browser/branding/nightly/wizHeaderRTL.bmp | Bin 25820 -> 34254 bytes browser/branding/nightly/wizWatermark.bmp | Bin 154544 -> 206038 bytes browser/branding/official/VisualElements_150.png | Bin 23037 -> 7949 bytes browser/branding/official/VisualElements_70.png | Bin 8763 -> 3374 bytes browser/branding/official/configure.sh | 16 +- .../official/content/identity-icons-brand.svg | 8 + browser/branding/official/content/jar.mn | 4 + browser/branding/official/content/tor-styles.css | 14 + browser/branding/official/default128.png | Bin 13513 -> 9007 bytes browser/branding/official/default16.png | Bin 722 -> 839 bytes browser/branding/official/default22.png | Bin 1134 -> 1250 bytes browser/branding/official/default24.png | Bin 1312 -> 1405 bytes browser/branding/official/default256.png | Bin 32441 -> 19136 bytes browser/branding/official/default32.png | Bin 1948 -> 1965 bytes browser/branding/official/default48.png | Bin 3448 -> 3074 bytes browser/branding/official/default512.png | Bin 0 -> 40438 bytes browser/branding/official/default64.png | Bin 5459 -> 4196 bytes browser/branding/official/disk.icns | Bin 1525764 -> 172073 bytes browser/branding/official/document.icns | Bin 501145 -> 509227 bytes browser/branding/official/document.ico | Bin 45478 -> 119916 bytes .../official/firefox.VisualElementsManifest.xml | 2 +- browser/branding/official/firefox.icns | Bin 1021785 -> 259709 bytes browser/branding/official/firefox.ico | Bin 68328 -> 118595 bytes browser/branding/official/firefox.svg | 31 + browser/branding/official/firefox64.ico | Bin 38630 -> 118595 bytes browser/branding/official/locales/en-US/brand.dtd | 8 +- .../official/locales/en-US/brand.properties | 10 +- browser/branding/official/wizHeader.bmp | Bin 25820 -> 34254 bytes browser/branding/official/wizHeaderRTL.bmp | Bin 25820 -> 34254 bytes browser/branding/official/wizWatermark.bmp | Bin 154544 -> 206038 bytes browser/branding/tor-styles.inc.css | 87 + browser/components/BrowserContentHandler.jsm | 94 +- browser/components/BrowserGlue.jsm | 208 ++- browser/components/about/AboutRedirector.cpp | 77 +- browser/components/about/components.conf | 8 +- .../components/aboutlogins/AboutLoginsParent.jsm | 2 + .../components/aboutlogins/content/aboutLogins.css | 8 +- .../components/aboutlogins/content/aboutLogins.js | 6 + .../content/components/fxaccounts-button.css | 5 + .../aboutlogins/content/components/menu-button.css | 10 + .../controlcenter/content/identityPanel.inc.xhtml | 22 + .../components/customizableui/CustomizableUI.jsm | 21 + .../customizableui/content/panelUI.inc.xhtml | 2 +- browser/components/moz.build | 11 +- browser/components/newtab/AboutNewTabService.jsm | 15 +- .../onionservices/ExtensionMessaging.jsm | 77 + .../onionservices/HttpsEverywhereControl.jsm | 127 ++ .../components/onionservices/OnionAliasStore.jsm | 520 ++++++ .../onionservices/OnionLocationChild.jsm | 48 + .../onionservices/OnionLocationParent.jsm | 168 ++ .../content/authNotificationIcon.inc.xhtml | 6 + .../onionservices/content/authPopup.inc.xhtml | 16 + .../onionservices/content/authPreferences.css | 20 + .../content/authPreferences.inc.xhtml | 19 + .../onionservices/content/authPreferences.js | 66 + .../components/onionservices/content/authPrompt.js | 320 ++++ .../components/onionservices/content/authUtil.jsm | 47 + .../onionservices/content/netError/browser.svg | 3 + .../onionservices/content/netError/network.svg | 3 + .../content/netError/onionNetError.css | 88 + .../content/netError/onionNetError.js | 243 +++ .../onionservices/content/netError/onionsite.svg | 8 + .../content/onionlocation-notification-icons.css | 5 + .../onionservices/content/onionlocation-urlbar.css | 60 + .../content/onionlocation-urlbar.inc.xhtml | 10 + .../onionservices/content/onionlocation.svg | 3 + .../content/onionlocationPreferences.inc.xhtml | 11 + .../content/onionlocationPreferences.js | 31 + .../onionservices/content/onionservices.css | 69 + .../onionservices/content/savedKeysDialog.js | 259 +++ .../onionservices/content/savedKeysDialog.xhtml | 42 + browser/components/onionservices/jar.mn | 11 + browser/components/onionservices/moz.build | 9 + browser/components/preferences/home.inc.xhtml | 4 +- browser/components/preferences/main.inc.xhtml | 54 - browser/components/preferences/main.js | 14 - browser/components/preferences/preferences.js | 14 +- browser/components/preferences/preferences.xhtml | 13 +- browser/components/preferences/privacy.inc.xhtml | 6 + browser/components/preferences/privacy.js | 44 + browser/components/rulesets/RulesetsChild.jsm | 11 + browser/components/rulesets/RulesetsParent.jsm | 79 + .../components/rulesets/content/aboutRulesets.css | 319 ++++ .../components/rulesets/content/aboutRulesets.html | 110 ++ .../components/rulesets/content/aboutRulesets.js | 531 ++++++ browser/components/rulesets/content/securedrop.svg | 173 ++ browser/components/rulesets/jar.mn | 5 + browser/components/rulesets/moz.build | 6 + browser/components/search/SearchSERPTelemetry.jsm | 6 - .../search/extensions/blockchair-onion/favicon.png | Bin 0 -> 3116 bytes .../extensions/blockchair-onion/manifest.json | 26 + .../search/extensions/blockchair/favicon.png | Bin 0 -> 2898 bytes .../search/extensions/blockchair/manifest.json | 26 + .../search/extensions/ddg-onion/favicon.ico | Bin 0 -> 973 bytes .../search/extensions/ddg-onion/manifest.json | 26 + .../components/search/extensions/ddg/favicon.ico | Bin 5430 -> 0 bytes .../components/search/extensions/ddg/favicon.png | Bin 0 -> 1150 bytes .../components/search/extensions/ddg/manifest.json | 38 +- .../extensions/google/_locales/b-1-d/messages.json | 23 - .../extensions/google/_locales/b-1-e/messages.json | 23 - .../extensions/google/_locales/b-d/messages.json | 23 - .../extensions/google/_locales/b-e/messages.json | 23 - .../extensions/google/_locales/en/messages.json | 24 - .../search/extensions/google/manifest.json | 17 +- .../search/extensions/startpage/favicon.png | Bin 0 -> 1150 bytes .../search/extensions/startpage/manifest.json | 26 + .../extensions/twitter/favicon.ico} | Bin .../search/extensions/twitter/manifest.json | 26 + .../extensions/wikipedia/_locales/NN/messages.json | 20 - .../extensions/wikipedia/_locales/NO/messages.json | 20 - .../extensions/wikipedia/_locales/af/messages.json | 20 - .../extensions/wikipedia/_locales/an/messages.json | 20 - .../extensions/wikipedia/_locales/ar/messages.json | 20 - .../wikipedia/_locales/ast/messages.json | 20 - .../extensions/wikipedia/_locales/az/messages.json | 20 - .../wikipedia/_locales/be-tarask/messages.json | 20 - .../extensions/wikipedia/_locales/be/messages.json | 20 - .../extensions/wikipedia/_locales/bg/messages.json | 20 - .../extensions/wikipedia/_locales/bn/messages.json | 20 - .../extensions/wikipedia/_locales/br/messages.json | 20 - .../extensions/wikipedia/_locales/bs/messages.json | 20 - .../extensions/wikipedia/_locales/ca/messages.json | 20 - .../extensions/wikipedia/_locales/cy/messages.json | 20 - .../extensions/wikipedia/_locales/cz/messages.json | 20 - .../extensions/wikipedia/_locales/da/messages.json | 20 - .../extensions/wikipedia/_locales/de/messages.json | 20 - .../wikipedia/_locales/dsb/messages.json | 20 - .../extensions/wikipedia/_locales/el/messages.json | 20 - .../extensions/wikipedia/_locales/en/messages.json | 20 - .../extensions/wikipedia/_locales/eo/messages.json | 20 - .../extensions/wikipedia/_locales/es/messages.json | 20 - .../extensions/wikipedia/_locales/et/messages.json | 20 - .../extensions/wikipedia/_locales/eu/messages.json | 20 - .../extensions/wikipedia/_locales/fa/messages.json | 20 - .../extensions/wikipedia/_locales/fi/messages.json | 20 - .../extensions/wikipedia/_locales/fr/messages.json | 20 - .../wikipedia/_locales/fy-NL/messages.json | 20 - .../wikipedia/_locales/ga-IE/messages.json | 20 - .../extensions/wikipedia/_locales/gd/messages.json | 20 - .../extensions/wikipedia/_locales/gl/messages.json | 20 - .../extensions/wikipedia/_locales/gn/messages.json | 20 - .../extensions/wikipedia/_locales/gu/messages.json | 20 - .../extensions/wikipedia/_locales/he/messages.json | 20 - .../extensions/wikipedia/_locales/hi/messages.json | 20 - .../extensions/wikipedia/_locales/hr/messages.json | 20 - .../wikipedia/_locales/hsb/messages.json | 20 - .../extensions/wikipedia/_locales/hu/messages.json | 20 - .../extensions/wikipedia/_locales/hy/messages.json | 20 - .../extensions/wikipedia/_locales/ia/messages.json | 20 - .../extensions/wikipedia/_locales/id/messages.json | 20 - .../extensions/wikipedia/_locales/is/messages.json | 20 - .../extensions/wikipedia/_locales/it/messages.json | 20 - .../extensions/wikipedia/_locales/ja/messages.json | 20 - .../extensions/wikipedia/_locales/ka/messages.json | 20 - .../wikipedia/_locales/kab/messages.json | 20 - .../extensions/wikipedia/_locales/kk/messages.json | 20 - .../extensions/wikipedia/_locales/km/messages.json | 20 - .../extensions/wikipedia/_locales/kn/messages.json | 20 - .../extensions/wikipedia/_locales/kr/messages.json | 20 - .../wikipedia/_locales/lij/messages.json | 20 - .../extensions/wikipedia/_locales/lo/messages.json | 20 - .../extensions/wikipedia/_locales/lt/messages.json | 20 - .../wikipedia/_locales/ltg/messages.json | 20 - .../extensions/wikipedia/_locales/lv/messages.json | 20 - .../extensions/wikipedia/_locales/mk/messages.json | 20 - .../extensions/wikipedia/_locales/mr/messages.json | 20 - .../extensions/wikipedia/_locales/ms/messages.json | 20 - .../extensions/wikipedia/_locales/my/messages.json | 20 - .../extensions/wikipedia/_locales/ne/messages.json | 20 - .../extensions/wikipedia/_locales/nl/messages.json | 20 - .../extensions/wikipedia/_locales/oc/messages.json | 20 - .../extensions/wikipedia/_locales/pa/messages.json | 20 - .../extensions/wikipedia/_locales/pl/messages.json | 20 - .../extensions/wikipedia/_locales/pt/messages.json | 20 - .../extensions/wikipedia/_locales/rm/messages.json | 20 - .../extensions/wikipedia/_locales/ro/messages.json | 20 - .../extensions/wikipedia/_locales/ru/messages.json | 20 - .../extensions/wikipedia/_locales/si/messages.json | 20 - .../extensions/wikipedia/_locales/sk/messages.json | 20 - .../extensions/wikipedia/_locales/sl/messages.json | 20 - .../extensions/wikipedia/_locales/sq/messages.json | 20 - .../extensions/wikipedia/_locales/sr/messages.json | 20 - .../wikipedia/_locales/sv-SE/messages.json | 20 - .../extensions/wikipedia/_locales/ta/messages.json | 20 - .../extensions/wikipedia/_locales/te/messages.json | 20 - .../extensions/wikipedia/_locales/th/messages.json | 20 - .../extensions/wikipedia/_locales/tl/messages.json | 20 - .../extensions/wikipedia/_locales/tr/messages.json | 20 - .../extensions/wikipedia/_locales/uk/messages.json | 20 - .../extensions/wikipedia/_locales/ur/messages.json | 20 - .../extensions/wikipedia/_locales/uz/messages.json | 20 - .../extensions/wikipedia/_locales/vi/messages.json | 20 - .../extensions/wikipedia/_locales/wo/messages.json | 20 - .../wikipedia/_locales/zh-CN/messages.json | 20 - .../wikipedia/_locales/zh-TW/messages.json | 20 - .../search/extensions/wikipedia/manifest.json | 15 +- .../components/search/extensions/yahoo/favicon.ico | Bin 0 -> 5430 bytes .../search/extensions/yahoo/manifest.json | 28 + .../search/extensions/youtube/favicon.ico | Bin 0 -> 1150 bytes .../search/extensions/youtube/manifest.json | 26 + .../securitylevel/content/securityLevel.js | 527 ++++++ .../securitylevel/content/securityLevelButton.css | 18 + .../content/securityLevelButton.inc.xhtml | 7 + .../securitylevel/content/securityLevelIcon.svg | 40 + .../securitylevel/content/securityLevelPanel.css | 74 + .../content/securityLevelPanel.inc.xhtml | 47 + .../content/securityLevelPreferences.css | 52 + .../content/securityLevelPreferences.inc.xhtml | 67 + browser/components/securitylevel/jar.mn | 6 + browser/components/securitylevel/moz.build | 1 + browser/components/sessionstore/SessionStore.jsm | 4 + browser/components/torconnect/TorConnectChild.jsm | 9 + browser/components/torconnect/TorConnectParent.jsm | 202 ++ .../torconnect/content/aboutTorConnect.css | 325 ++++ .../torconnect/content/aboutTorConnect.js | 843 +++++++++ .../torconnect/content/aboutTorConnect.xhtml | 68 + .../components/torconnect/content/arrow-right.svg | 4 + browser/components/torconnect/content/bridge.svg | 5 + .../torconnect/content/connection-failure.svg | 5 + .../torconnect/content/connection-location.svg | 5 + .../torconnect/content/onion-slash-fillable.svg | 5 + .../components/torconnect/content/onion-slash.svg | 5 + browser/components/torconnect/content/onion.svg | 4 + .../torconnect/content/torBootstrapUrlbar.js | 93 + .../torconnect/content/torconnect-urlbar.css | 57 + .../torconnect/content/torconnect-urlbar.inc.xhtml | 10 + browser/components/torconnect/jar.mn | 12 + browser/components/torconnect/moz.build | 6 + .../torpreferences/content/bridgeQrDialog.jsm | 51 + .../torpreferences/content/bridgeQrDialog.xhtml | 23 + .../torpreferences/content/builtinBridgeDialog.jsm | 142 ++ .../content/builtinBridgeDialog.xhtml | 43 + .../content/connectionCategory.inc.xhtml | 9 + .../torpreferences/content/connectionPane.js | 1137 ++++++++++++ .../torpreferences/content/connectionPane.xhtml | 194 ++ .../content/connectionSettingsDialog.jsm | 393 ++++ .../content/connectionSettingsDialog.xhtml | 62 + .../components/torpreferences/content/network.svg | 0 .../torpreferences/content/provideBridgeDialog.jsm | 75 + .../content/provideBridgeDialog.xhtml | 21 + .../torpreferences/content/requestBridgeDialog.jsm | 211 +++ .../content/requestBridgeDialog.xhtml | 35 + .../torpreferences/content/torLogDialog.jsm | 84 + .../torpreferences/content/torLogDialog.xhtml | 23 + .../torpreferences/content/torPreferences.css | 655 +++++++ .../torpreferences/content/torPreferencesIcon.svg | 8 + browser/components/torpreferences/jar.mn | 19 + browser/components/torpreferences/moz.build | 1 + browser/components/uitour/UITour-lib.js | 7 + browser/components/uitour/UITour.jsm | 120 +- browser/components/uitour/UITourChild.jsm | 33 +- browser/components/urlbar/UrlbarInput.jsm | 32 + browser/config/mozconfigs/base-browser | 34 + browser/config/mozconfigs/tor-browser | 10 + browser/confvars.sh | 35 +- browser/extensions/moz.build | 13 +- .../extensions/onboarding/OnboardingTelemetry.jsm | 578 ++++++ .../extensions/onboarding/OnboardingTourType.jsm | 40 + browser/extensions/onboarding/README.md | 87 + browser/extensions/onboarding/api.js | 275 +++ browser/extensions/onboarding/background.js | 8 + .../extensions/onboarding/content/Onboarding.jsm | 1920 ++++++++++++++++++++ .../extensions/onboarding/content/img/close.png | Bin 0 -> 798 bytes .../content/img/figure_tor-circuit-display.png | Bin 0 -> 26334 bytes .../content/img/figure_tor-expect-differences.png | Bin 0 -> 22290 bytes .../onboarding/content/img/figure_tor-network.png | Bin 0 -> 11982 bytes .../content/img/figure_tor-onion-services.png | Bin 0 -> 40968 bytes .../onboarding/content/img/figure_tor-privacy.png | Bin 0 -> 35527 bytes .../content/img/figure_tor-security-level.png | Bin 0 -> 11263 bytes .../onboarding/content/img/figure_tor-security.png | Bin 0 -> 24554 bytes .../content/img/figure_tor-toolbar-layout.png | Bin 0 -> 13269 bytes .../onboarding/content/img/figure_tor-welcome.png | Bin 0 -> 48405 bytes .../onboarding/content/img/icons_no-icon.png | Bin 0 -> 673 bytes .../onboarding/content/img/icons_tour-complete.png | Bin 0 -> 694 bytes .../onboarding/content/img/icons_tour-complete.svg | 17 + .../content/onboarding-tor-circuit-display.js | 283 +++ .../onboarding/content/onboarding-tour-agent.js | 107 ++ .../extensions/onboarding/content/onboarding.css | 664 +++++++ .../extensions/onboarding/content/onboarding.js | 38 + browser/extensions/onboarding/data_events.md | 154 ++ browser/extensions/onboarding/jar.mn | 19 + .../onboarding/locales/en-US/onboarding.properties | 126 ++ .../extensions/onboarding/locales/jar.mn | 11 +- .../onboarding/locales}/moz.build | 0 browser/extensions/onboarding/manifest.json | 26 + browser/extensions/onboarding/moz.build | 29 + .../{formautofill => onboarding}/schema.json | 0 .../onboarding/test/browser/.eslintrc.js | 7 + .../extensions/onboarding/test/browser/browser.ini | 18 + .../browser/browser_onboarding_accessibility.js | 89 + .../test/browser/browser_onboarding_keyboard.js | 137 ++ .../browser/browser_onboarding_notification.js | 62 + .../browser/browser_onboarding_notification_2.js | 80 + .../browser/browser_onboarding_notification_3.js | 82 + .../browser/browser_onboarding_notification_4.js | 84 + .../browser/browser_onboarding_notification_5.js | 25 + ...arding_notification_click_auto_complete_tour.js | 33 + .../browser_onboarding_select_default_tour.js | 80 + .../test/browser/browser_onboarding_skip_tour.js | 47 + .../test/browser/browser_onboarding_tours.js | 115 ++ .../test/browser/browser_onboarding_tourset.js | 82 + .../test/browser/browser_onboarding_uitour.js | 167 ++ browser/extensions/onboarding/test/browser/head.js | 288 +++ .../extensions/onboarding/test/unit/.eslintrc.js | 7 + browser/extensions/onboarding/test/unit/head.js | 54 + .../test/unit/test-onboarding-tour-type.js | 89 + .../extensions/onboarding/test/unit/xpcshell.ini | 5 + browser/installer/Makefile.in | 8 + browser/installer/package-manifest.in | 18 +- browser/installer/windows/nsis/shared.nsh | 1 - browser/locales/Makefile.in | 10 +- .../locales/en-US/chrome/browser/aboutTBUpdate.dtd | 8 + browser/locales/filter.py | 1 + browser/locales/jar.mn | 10 +- browser/locales/l10n.ini | 1 + browser/locales/l10n.toml | 4 + browser/modules/BridgeDB.jsm | 61 + browser/modules/HomePage.jsm | 2 +- browser/modules/Moat.jsm | 814 +++++++++ browser/modules/TorConnect.jsm | 901 +++++++++ browser/modules/TorProcessService.jsm | 12 + browser/modules/TorProtocolService.jsm | 502 +++++ browser/modules/TorSettings.jsm | 674 +++++++ browser/modules/TorStrings.jsm | 918 ++++++++++ browser/modules/moz.build | 7 + browser/moz.build | 1 + browser/moz.configure | 8 +- browser/themes/linux/browser.css | 9 - browser/themes/shared/UITour.inc.css | 56 +- browser/themes/shared/browser.inc.css | 5 + browser/themes/shared/controlcenter/panel.inc.css | 4 +- .../themes/shared/customizableui/panelUI.inc.css | 3 +- browser/themes/shared/icons/new_circuit.svg | 6 + browser/themes/shared/icons/new_identity.svg | 9 + .../shared/identity-block/identity-block.inc.css | 33 +- .../themes/shared/identity-block/onion-slash.svg | 5 + .../themes/shared/identity-block/onion-warning.svg | 4 + browser/themes/shared/identity-block/onion.svg | 4 + browser/themes/shared/jar.inc.mn | 8 + browser/themes/shared/menupanel.inc.css | 1 + browser/themes/shared/notification-icons.inc.css | 5 + browser/themes/shared/onionPattern.css | 31 + browser/themes/shared/onionPattern.inc.xhtml | 12 + browser/themes/shared/onionPattern.svg | 22 + browser/themes/shared/preferences/privacy.css | 4 + browser/themes/shared/tabs.inc.css | 6 + browser/themes/shared/toolbarbutton-icons.inc.css | 8 + browser/themes/shared/urlbar-searchbar.inc.css | 4 + browser/themes/windows/browser.css | 9 - build/application.ini.in | 2 +- build/moz.configure/init.configure | 3 +- config/createprecomplete.py | 18 +- .../client/aboutdebugging/src/actions/runtimes.js | 5 + .../images/aboutdebugging-firefox-aurora.svg | 35 +- .../themes/images/aboutdebugging-firefox-beta.svg | 35 +- .../themes/images/aboutdebugging-firefox-logo.svg | 11 +- .../images/aboutdebugging-firefox-nightly.svg | 35 +- .../images/aboutdebugging-firefox-release.svg | 35 +- docshell/base/nsAboutRedirector.cpp | 6 +- docshell/base/nsDocShell.cpp | 81 +- docshell/build/components.conf | 1 + dom/base/Document.cpp | 97 +- dom/base/Document.h | 10 +- dom/base/nsContentUtils.cpp | 19 + dom/base/nsContentUtils.h | 5 + dom/base/nsGlobalWindowOuter.cpp | 5 +- dom/base/test/mochitest.ini | 2 - dom/base/test/test_suppressed_microtasks.html | 62 - dom/canvas/ClientWebGLContext.cpp | 8 + dom/ipc/BrowserParent.cpp | 21 + dom/ipc/BrowserParent.h | 3 + dom/ipc/PBrowser.ipdl | 9 + dom/ipc/WindowGlobalActor.cpp | 4 +- dom/ipc/WindowGlobalChild.cpp | 6 +- dom/notification/old/NotificationDB.jsm | 19 +- dom/security/nsMixedContentBlocker.cpp | 16 +- dom/webidl/Document.webidl | 8 + dom/workers/RuntimeService.cpp | 4 +- dom/workers/WorkerPrivate.cpp | 2 +- dom/worklet/WorkletThread.cpp | 2 +- extensions/auth/nsHttpNegotiateAuth.cpp | 4 + extensions/permissions/PermissionManager.cpp | 6 +- intl/l10n/L10nRegistry.jsm | 19 +- intl/locale/LocaleService.cpp | 6 + intl/strres/nsStringBundle.cpp | 1 + js/src/builtin/Array.cpp | 32 + js/src/builtin/Array.h | 2 + js/src/builtin/Module.js | 17 +- js/src/builtin/RegExp.js | 6 +- js/src/builtin/Utilities.js | 11 +- js/src/vm/SelfHosting.cpp | 3 +- js/xpconnect/src/xpc.msg | 10 + layout/generic/ReflowInput.cpp | 19 +- layout/generic/test/mochitest.ini | 1 + layout/generic/test/test_tor_bug23104.html | 50 + mobile/android/app/000-tor-browser-android.js | 47 + mobile/android/app/geckoview-prefs.js | 2 + mobile/android/app/mobile.js | 4 + mobile/android/app/moz.build | 1 + .../components/geckoview/GeckoViewStartup.jsm | 5 + mobile/android/confvars.sh | 9 + mobile/android/geckoview/api.txt | 6 + mobile/android/geckoview/build.gradle | 1 + .../android/geckoview/src/main/AndroidManifest.xml | 20 +- .../gecko/media/GeckoMediaDrmBridgeV21.java | 49 +- .../java/org/mozilla/gecko/util/ProxySelector.java | 25 +- .../java/org/mozilla/geckoview/GeckoRuntime.java | 8 +- .../mozilla/geckoview/GeckoRuntimeSettings.java | 94 +- .../java/org/mozilla/geckoview/WebRequest.java | 18 + .../exoplayer2/upstream/DefaultHttpDataSource.java | 47 +- mobile/android/installer/package-manifest.in | 4 + .../modules/geckoview/GeckoViewProgress.jsm | 4 + mobile/android/moz.configure | 21 +- mobile/android/torbrowser.configure | 30 + modules/libmar/tool/mar.c | 6 +- modules/libmar/tool/moz.build | 12 +- modules/libmar/verify/moz.build | 14 +- modules/libpref/init/StaticPrefList.yaml | 5 + moz.configure | 100 + mozconfig-android-armv7 | 37 + mozconfig-linux-i686 | 9 + mozconfig-linux-x86_64 | 9 + mozconfig-linux-x86_64-asan | 22 + mozconfig-linux-x86_64-dev | 14 + mozconfig-macos-x86_64 | 5 + mozconfig-windows-i686 | 11 + mozconfig-windows-x86_64 | 11 + netwerk/base/nsSocketTransport2.cpp | 6 + netwerk/build/components.conf | 11 + netwerk/build/nsNetCID.h | 10 + netwerk/dns/IOnionAliasService.idl | 34 + netwerk/dns/OnionAliasService.cpp | 94 + netwerk/dns/OnionAliasService.h | 36 + netwerk/dns/TRR.cpp | 2 + netwerk/dns/effective_tld_names.dat | 2 + netwerk/dns/mdns/libmdns/components.conf | 15 - netwerk/dns/mdns/libmdns/moz.build | 28 - netwerk/dns/moz.build | 4 + netwerk/protocol/http/nsHttpNTLMAuth.cpp | 3 + netwerk/socket/nsSOCKSIOLayer.cpp | 73 +- .../url-classifier/UrlClassifierFeatureBase.cpp | 2 +- netwerk/url-classifier/components.conf | 6 - python/mozbuild/mozbuild/artifacts.py | 2 - run-tbb-tests | 66 + security/manager/ssl/RemoteSecuritySettings.jsm | 23 + security/manager/ssl/SSLServerCertVerification.cpp | 9 + security/manager/ssl/SSLServerCertVerification.h | 4 +- security/manager/ssl/nsSecureBrowserUI.cpp | 12 + security/moz.build | 2 +- security/nss/lib/certhigh/ocsp.c | 4 + .../libpkix/pkix_pl_nss/module/pkix_pl_socket.c | 12 + security/nss/lib/ssl/Makefile | 2 +- services/settings/IDBHelpers.jsm | 4 + services/settings/dumps/blocklists/moz.build | 14 +- services/settings/dumps/main/moz.build | 7 - services/settings/dumps/security-state/moz.build | 1 - taskcluster/ci/source-test/mozlint.yml | 1 + tbb-tests-ignore.txt | 13 + tbb-tests/browser.ini | 5 + tbb-tests/browser_tor_TB4.js | 35 + tbb-tests/browser_tor_bug2950.js | 74 + tbb-tests/browser_tor_omnibox.js | 20 + tbb-tests/mochitest.ini | 3 + .../nightly/locales => tbb-tests}/moz.build | 6 +- tbb-tests/test_tor_bug2874.html | 25 + toolkit/actors/AboutHttpsOnlyErrorParent.jsm | 5 + .../components/antitracking/antitracking.manifest | 2 +- toolkit/components/antitracking/components.conf | 7 - .../EnterprisePoliciesParent.jsm | 13 +- toolkit/components/enterprisepolicies/moz.build | 3 + toolkit/components/extensions/Extension.jsm | 19 +- toolkit/components/extensions/ExtensionParent.jsm | 47 + .../components/httpsonlyerror/content/errorpage.js | 19 +- .../processsingleton/MainProcessSingleton.jsm | 10 + .../remotebrowserutils/RemoteWebNavigation.jsm | 4 + .../components/resistfingerprinting/RFPHelper.jsm | 94 +- toolkit/components/search/SearchService.jsm | 32 +- toolkit/components/telemetry/app/TelemetrySend.jsm | 19 +- toolkit/components/telemetry/moz.build | 4 - toolkit/content/license.html | 65 + .../mozapps/profile/profileSelection.properties | 5 + toolkit/modules/AppConstants.jsm | 22 + toolkit/modules/Bech32Decode.jsm | 103 ++ toolkit/modules/PopupNotifications.jsm | 6 + toolkit/modules/QRCode.jsm | 1241 +++++++++++++ toolkit/modules/RemotePageAccessManager.jsm | 41 + toolkit/modules/UpdateUtils.jsm | 22 +- toolkit/modules/moz.build | 5 + toolkit/moz.build | 1 + toolkit/moz.configure | 18 + toolkit/mozapps/extensions/AddonManager.jsm | 24 + .../mozapps/extensions/internal/XPIProvider.jsm | 46 +- toolkit/mozapps/extensions/test/browser/head.js | 1 + .../extensions/test/xpcshell/head_addons.js | 1 + toolkit/mozapps/update/UpdateService.jsm | 256 ++- toolkit/mozapps/update/UpdateServiceStub.jsm | 4 + toolkit/mozapps/update/UpdateTelemetry.jsm | 1 + toolkit/mozapps/update/common/updatehelper.cpp | 8 + toolkit/mozapps/update/moz.build | 5 +- toolkit/mozapps/update/nsIUpdateService.idl | 11 + toolkit/mozapps/update/updater/launchchild_osx.mm | 2 + toolkit/mozapps/update/updater/moz.build | 2 +- .../updater/nightly_aurora_level3_primary.der | Bin 1225 -> 1245 bytes .../updater/nightly_aurora_level3_secondary.der | Bin 1225 -> 1245 bytes toolkit/mozapps/update/updater/release_primary.der | Bin 1225 -> 1229 bytes .../mozapps/update/updater/release_secondary.der | Bin 1225 -> 1229 bytes .../mozapps/update/updater/updater-common.build | 24 +- toolkit/mozapps/update/updater/updater.cpp | 364 +++- toolkit/profile/nsProfileLock.cpp | 11 + toolkit/profile/nsToolkitProfileService.cpp | 57 +- toolkit/profile/nsToolkitProfileService.h | 13 +- toolkit/toolkit.mozbuild | 3 +- toolkit/torproject/torbutton | 1 + toolkit/xre/MacLaunchHelper.h | 2 + toolkit/xre/MacLaunchHelper.mm | 2 + toolkit/xre/moz.build | 3 + toolkit/xre/nsAppRunner.cpp | 270 ++- toolkit/xre/nsUpdateDriver.cpp | 159 +- toolkit/xre/nsXREDirProvider.cpp | 144 +- toolkit/xre/nsXREDirProvider.h | 14 + tools/lint/codespell.yml | 1 + .../lib/environments/browser-window.js | 10 +- .../lib/environments/frame-script.js | 1 + tools/update-packaging/common.sh | 64 +- tools/update-packaging/make_full_update.sh | 25 + tools/update-packaging/make_incremental_update.sh | 71 +- .../exthandler/nsExternalHelperAppService.cpp | 207 ++- uriloader/exthandler/nsExternalHelperAppService.h | 3 + .../exthandler/nsIExternalHelperAppService.idl | 47 + widget/android/WebExecutorSupport.cpp | 10 + widget/nsTransferable.cpp | 6 + xpcom/base/CycleCollectedJSContext.cpp | 51 +- xpcom/base/CycleCollectedJSContext.h | 29 +- xpcom/base/ErrorList.py | 22 + xpcom/ds/StaticAtoms.py | 1 + xpcom/io/TorFileUtils.cpp | 96 + xpcom/io/TorFileUtils.h | 32 + xpcom/io/moz.build | 5 + xpcom/io/nsAppFileLocationProvider.cpp | 72 +- 664 files changed, 27574 insertions(+), 3162 deletions(-) create mode 100644 .gitlab/issue_templates/UXBug.md create mode 100644 .gitlab/issue_templates/bug.md create mode 100644 .gitmodules create mode 100644 browser/actors/AboutTBUpdateChild.jsm create mode 100644 browser/actors/AboutTBUpdateParent.jsm create mode 100644 browser/actors/CryptoSafetyChild.jsm create mode 100644 browser/actors/CryptoSafetyParent.jsm create mode 100644 browser/app/profile/000-tor-browser.js create mode 100644 browser/app/profile/001-base-profile.js create mode 100644 browser/base/content/abouttbupdate/aboutTBUpdate.css create mode 100644 browser/base/content/abouttbupdate/aboutTBUpdate.js create mode 100644 browser/base/content/abouttbupdate/aboutTBUpdate.xhtml create mode 100644 browser/branding/alpha/VisualElements_150.png create mode 100644 browser/branding/alpha/VisualElements_70.png create mode 100644 browser/branding/alpha/background.png create mode 100644 browser/branding/alpha/bgstub.jpg create mode 100644 browser/branding/alpha/bgstub_2x.jpg copy browser/branding/{nightly => alpha}/branding.nsi (100%) copy services/settings/dumps/security-state/moz.build => browser/branding/alpha/configure.sh (53%) create mode 100644 browser/branding/alpha/content/about-logo.png copy browser/branding/{nightly => alpha}/content/about-logo.svg (100%) create mode 100644 browser/branding/alpha/content/about-logo@2x.png create mode 100644 browser/branding/alpha/content/about-wordmark.svg create mode 100644 browser/branding/alpha/content/about.png copy browser/branding/{nightly => alpha}/content/aboutDialog.css (100%) create mode 100644 browser/branding/alpha/content/aboutlogins.svg copy browser/branding/{nightly => alpha}/content/firefox-wordmark.svg (100%) create mode 100644 browser/branding/alpha/content/identity-icons-brand.svg copy browser/branding/{official => alpha}/content/jar.mn (80%) copy browser/branding/{aurora => alpha}/content/moz.build (100%) create mode 100644 browser/branding/alpha/content/tor-styles.css create mode 100644 browser/branding/alpha/default128.png create mode 100644 browser/branding/alpha/default16.png create mode 100644 browser/branding/alpha/default22.png create mode 100644 browser/branding/alpha/default24.png create mode 100644 browser/branding/alpha/default256.png create mode 100644 browser/branding/alpha/default32.png create mode 100644 browser/branding/alpha/default48.png create mode 100644 browser/branding/alpha/default512.png create mode 100644 browser/branding/alpha/default64.png create mode 100644 browser/branding/alpha/disk.icns create mode 100644 browser/branding/alpha/document.icns create mode 100644 browser/branding/alpha/document.ico copy browser/branding/{nightly => alpha}/dsstore (100%) copy browser/branding/{nightly => alpha}/firefox.VisualElementsManifest.xml (93%) create mode 100644 browser/branding/alpha/firefox.icns create mode 100644 browser/branding/alpha/firefox.ico create mode 100644 browser/branding/alpha/firefox.svg create mode 100644 browser/branding/alpha/firefox64.ico copy browser/branding/{official => alpha}/locales/en-US/brand.dtd (68%) copy browser/branding/{nightly => alpha}/locales/en-US/brand.ftl (90%) copy browser/branding/{nightly => alpha}/locales/en-US/brand.properties (69%) copy browser/branding/{official => alpha}/locales/jar.mn (100%) copy browser/branding/{aurora/content => alpha/locales}/moz.build (100%) copy browser/branding/{aurora => alpha}/moz.build (100%) copy browser/branding/{aurora => alpha}/newtab.ico (100%) copy browser/branding/{aurora => alpha}/newwindow.ico (100%) copy browser/branding/{aurora => alpha}/pbmode.ico (100%) create mode 100644 browser/branding/alpha/pref/firefox-branding.js copy browser/branding/{nightly => alpha}/stubinstaller/bgstub.jpg (100%) copy browser/branding/{nightly => alpha}/stubinstaller/installing_page.css (100%) copy browser/branding/{aurora => alpha}/stubinstaller/profile_cleanup_page.css (100%) create mode 100644 browser/branding/alpha/wizHeader.bmp create mode 100644 browser/branding/alpha/wizHeaderRTL.bmp create mode 100644 browser/branding/alpha/wizWatermark.bmp create mode 100644 browser/branding/nightly/content/identity-icons-brand.svg create mode 100644 browser/branding/nightly/content/tor-styles.css create mode 100644 browser/branding/nightly/default512.png create mode 100644 browser/branding/nightly/firefox.svg create mode 100644 browser/branding/official/content/identity-icons-brand.svg create mode 100644 browser/branding/official/content/tor-styles.css create mode 100644 browser/branding/official/default512.png create mode 100644 browser/branding/official/firefox.svg create mode 100644 browser/branding/tor-styles.inc.css create mode 100644 browser/components/onionservices/ExtensionMessaging.jsm create mode 100644 browser/components/onionservices/HttpsEverywhereControl.jsm create mode 100644 browser/components/onionservices/OnionAliasStore.jsm create mode 100644 browser/components/onionservices/OnionLocationChild.jsm create mode 100644 browser/components/onionservices/OnionLocationParent.jsm create mode 100644 browser/components/onionservices/content/authNotificationIcon.inc.xhtml create mode 100644 browser/components/onionservices/content/authPopup.inc.xhtml create mode 100644 browser/components/onionservices/content/authPreferences.css create mode 100644 browser/components/onionservices/content/authPreferences.inc.xhtml create mode 100644 browser/components/onionservices/content/authPreferences.js create mode 100644 browser/components/onionservices/content/authPrompt.js create mode 100644 browser/components/onionservices/content/authUtil.jsm create mode 100644 browser/components/onionservices/content/netError/browser.svg create mode 100644 browser/components/onionservices/content/netError/network.svg create mode 100644 browser/components/onionservices/content/netError/onionNetError.css create mode 100644 browser/components/onionservices/content/netError/onionNetError.js create mode 100644 browser/components/onionservices/content/netError/onionsite.svg create mode 100644 browser/components/onionservices/content/onionlocation-notification-icons.css create mode 100644 browser/components/onionservices/content/onionlocation-urlbar.css create mode 100644 browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml create mode 100644 browser/components/onionservices/content/onionlocation.svg create mode 100644 browser/components/onionservices/content/onionlocationPreferences.inc.xhtml create mode 100644 browser/components/onionservices/content/onionlocationPreferences.js create mode 100644 browser/components/onionservices/content/onionservices.css create mode 100644 browser/components/onionservices/content/savedKeysDialog.js create mode 100644 browser/components/onionservices/content/savedKeysDialog.xhtml create mode 100644 browser/components/onionservices/jar.mn create mode 100644 browser/components/onionservices/moz.build create mode 100644 browser/components/rulesets/RulesetsChild.jsm create mode 100644 browser/components/rulesets/RulesetsParent.jsm create mode 100644 browser/components/rulesets/content/aboutRulesets.css create mode 100644 browser/components/rulesets/content/aboutRulesets.html create mode 100644 browser/components/rulesets/content/aboutRulesets.js create mode 100644 browser/components/rulesets/content/securedrop.svg create mode 100644 browser/components/rulesets/jar.mn create mode 100644 browser/components/rulesets/moz.build create mode 100644 browser/components/search/extensions/blockchair-onion/favicon.png create mode 100644 browser/components/search/extensions/blockchair-onion/manifest.json create mode 100644 browser/components/search/extensions/blockchair/favicon.png create mode 100644 browser/components/search/extensions/blockchair/manifest.json create mode 100644 browser/components/search/extensions/ddg-onion/favicon.ico create mode 100644 browser/components/search/extensions/ddg-onion/manifest.json delete mode 100644 browser/components/search/extensions/ddg/favicon.ico create mode 100644 browser/components/search/extensions/ddg/favicon.png delete mode 100644 browser/components/search/extensions/google/_locales/b-1-d/messages.json delete mode 100644 browser/components/search/extensions/google/_locales/b-1-e/messages.json delete mode 100644 browser/components/search/extensions/google/_locales/b-d/messages.json delete mode 100644 browser/components/search/extensions/google/_locales/b-e/messages.json delete mode 100644 browser/components/search/extensions/google/_locales/en/messages.json create mode 100644 browser/components/search/extensions/startpage/favicon.png create mode 100644 browser/components/search/extensions/startpage/manifest.json copy browser/components/{newtab/data/content/tippytop/favicons/twitter-com.ico => search/extensions/twitter/favicon.ico} (100%) create mode 100644 browser/components/search/extensions/twitter/manifest.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/NN/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/NO/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/af/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/an/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ar/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ast/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/az/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/be-tarask/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/be/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/bg/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/bn/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/br/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/bs/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ca/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/cy/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/cz/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/da/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/de/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/dsb/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/el/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/en/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/eo/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/es/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/et/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/eu/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/fa/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/fi/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/fr/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/fy-NL/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ga-IE/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/gd/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/gl/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/gn/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/gu/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/he/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/hi/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/hr/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/hsb/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/hu/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/hy/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ia/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/id/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/is/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/it/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ja/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ka/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/kab/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/kk/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/km/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/kn/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/kr/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/lij/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/lo/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/lt/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ltg/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/lv/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/mk/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/mr/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ms/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/my/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ne/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/nl/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/oc/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/pa/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/pl/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/pt/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/rm/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ro/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ru/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/si/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/sk/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/sl/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/sq/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/sr/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/sv-SE/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ta/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/te/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/th/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/tl/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/tr/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/uk/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/ur/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/uz/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/vi/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/wo/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/zh-CN/messages.json delete mode 100644 browser/components/search/extensions/wikipedia/_locales/zh-TW/messages.json create mode 100644 browser/components/search/extensions/yahoo/favicon.ico create mode 100644 browser/components/search/extensions/yahoo/manifest.json create mode 100644 browser/components/search/extensions/youtube/favicon.ico create mode 100644 browser/components/search/extensions/youtube/manifest.json create mode 100644 browser/components/securitylevel/content/securityLevel.js create mode 100644 browser/components/securitylevel/content/securityLevelButton.css create mode 100644 browser/components/securitylevel/content/securityLevelButton.inc.xhtml create mode 100644 browser/components/securitylevel/content/securityLevelIcon.svg create mode 100644 browser/components/securitylevel/content/securityLevelPanel.css create mode 100644 browser/components/securitylevel/content/securityLevelPanel.inc.xhtml create mode 100644 browser/components/securitylevel/content/securityLevelPreferences.css create mode 100644 browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml create mode 100644 browser/components/securitylevel/jar.mn create mode 100644 browser/components/securitylevel/moz.build create mode 100644 browser/components/torconnect/TorConnectChild.jsm create mode 100644 browser/components/torconnect/TorConnectParent.jsm create mode 100644 browser/components/torconnect/content/aboutTorConnect.css create mode 100644 browser/components/torconnect/content/aboutTorConnect.js create mode 100644 browser/components/torconnect/content/aboutTorConnect.xhtml create mode 100644 browser/components/torconnect/content/arrow-right.svg create mode 100644 browser/components/torconnect/content/bridge.svg create mode 100644 browser/components/torconnect/content/connection-failure.svg create mode 100644 browser/components/torconnect/content/connection-location.svg create mode 100644 browser/components/torconnect/content/onion-slash-fillable.svg create mode 100644 browser/components/torconnect/content/onion-slash.svg create mode 100644 browser/components/torconnect/content/onion.svg create mode 100644 browser/components/torconnect/content/torBootstrapUrlbar.js create mode 100644 browser/components/torconnect/content/torconnect-urlbar.css create mode 100644 browser/components/torconnect/content/torconnect-urlbar.inc.xhtml create mode 100644 browser/components/torconnect/jar.mn create mode 100644 browser/components/torconnect/moz.build create mode 100644 browser/components/torpreferences/content/bridgeQrDialog.jsm create mode 100644 browser/components/torpreferences/content/bridgeQrDialog.xhtml create mode 100644 browser/components/torpreferences/content/builtinBridgeDialog.jsm create mode 100644 browser/components/torpreferences/content/builtinBridgeDialog.xhtml create mode 100644 browser/components/torpreferences/content/connectionCategory.inc.xhtml create mode 100644 browser/components/torpreferences/content/connectionPane.js create mode 100644 browser/components/torpreferences/content/connectionPane.xhtml create mode 100644 browser/components/torpreferences/content/connectionSettingsDialog.jsm create mode 100644 browser/components/torpreferences/content/connectionSettingsDialog.xhtml copy toolkit/themes/shared/icons/defaultFavicon.svg => browser/components/torpreferences/content/network.svg (100%) create mode 100644 browser/components/torpreferences/content/provideBridgeDialog.jsm create mode 100644 browser/components/torpreferences/content/provideBridgeDialog.xhtml create mode 100644 browser/components/torpreferences/content/requestBridgeDialog.jsm create mode 100644 browser/components/torpreferences/content/requestBridgeDialog.xhtml create mode 100644 browser/components/torpreferences/content/torLogDialog.jsm create mode 100644 browser/components/torpreferences/content/torLogDialog.xhtml create mode 100644 browser/components/torpreferences/content/torPreferences.css create mode 100644 browser/components/torpreferences/content/torPreferencesIcon.svg create mode 100644 browser/components/torpreferences/jar.mn create mode 100644 browser/components/torpreferences/moz.build create mode 100644 browser/config/mozconfigs/base-browser create mode 100644 browser/config/mozconfigs/tor-browser create mode 100644 browser/extensions/onboarding/OnboardingTelemetry.jsm create mode 100644 browser/extensions/onboarding/OnboardingTourType.jsm create mode 100644 browser/extensions/onboarding/README.md create mode 100644 browser/extensions/onboarding/api.js create mode 100644 browser/extensions/onboarding/background.js create mode 100644 browser/extensions/onboarding/content/Onboarding.jsm create mode 100644 browser/extensions/onboarding/content/img/close.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-circuit-display.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-expect-differences.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-network.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-onion-services.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-privacy.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-security-level.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-security.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-toolbar-layout.png create mode 100644 browser/extensions/onboarding/content/img/figure_tor-welcome.png create mode 100644 browser/extensions/onboarding/content/img/icons_no-icon.png create mode 100644 browser/extensions/onboarding/content/img/icons_tour-complete.png create mode 100644 browser/extensions/onboarding/content/img/icons_tour-complete.svg create mode 100644 browser/extensions/onboarding/content/onboarding-tor-circuit-display.js create mode 100644 browser/extensions/onboarding/content/onboarding-tour-agent.js create mode 100644 browser/extensions/onboarding/content/onboarding.css create mode 100644 browser/extensions/onboarding/content/onboarding.js create mode 100644 browser/extensions/onboarding/data_events.md create mode 100644 browser/extensions/onboarding/jar.mn create mode 100644 browser/extensions/onboarding/locales/en-US/onboarding.properties copy services/settings/dumps/security-state/moz.build => browser/extensions/onboarding/locales/jar.mn (53%) copy browser/{branding/aurora/content => extensions/onboarding/locales}/moz.build (100%) create mode 100644 browser/extensions/onboarding/manifest.json create mode 100644 browser/extensions/onboarding/moz.build copy browser/extensions/{formautofill => onboarding}/schema.json (100%) create mode 100644 browser/extensions/onboarding/test/browser/.eslintrc.js create mode 100644 browser/extensions/onboarding/test/browser/browser.ini create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_notification.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_tours.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js create mode 100644 browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js create mode 100644 browser/extensions/onboarding/test/browser/head.js create mode 100644 browser/extensions/onboarding/test/unit/.eslintrc.js create mode 100644 browser/extensions/onboarding/test/unit/head.js create mode 100644 browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js create mode 100644 browser/extensions/onboarding/test/unit/xpcshell.ini create mode 100644 browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd create mode 100644 browser/modules/BridgeDB.jsm create mode 100644 browser/modules/Moat.jsm create mode 100644 browser/modules/TorConnect.jsm create mode 100644 browser/modules/TorProcessService.jsm create mode 100644 browser/modules/TorProtocolService.jsm create mode 100644 browser/modules/TorSettings.jsm create mode 100644 browser/modules/TorStrings.jsm create mode 100644 browser/themes/shared/icons/new_circuit.svg create mode 100644 browser/themes/shared/icons/new_identity.svg create mode 100644 browser/themes/shared/identity-block/onion-slash.svg create mode 100644 browser/themes/shared/identity-block/onion-warning.svg create mode 100644 browser/themes/shared/identity-block/onion.svg create mode 100644 browser/themes/shared/onionPattern.css create mode 100644 browser/themes/shared/onionPattern.inc.xhtml create mode 100644 browser/themes/shared/onionPattern.svg delete mode 100644 dom/base/test/test_suppressed_microtasks.html create mode 100644 layout/generic/test/test_tor_bug23104.html create mode 100644 mobile/android/app/000-tor-browser-android.js create mode 100644 mobile/android/torbrowser.configure create mode 100644 mozconfig-android-armv7 create mode 100644 mozconfig-linux-i686 create mode 100644 mozconfig-linux-x86_64 create mode 100644 mozconfig-linux-x86_64-asan create mode 100644 mozconfig-linux-x86_64-dev create mode 100644 mozconfig-macos-x86_64 create mode 100644 mozconfig-windows-i686 create mode 100644 mozconfig-windows-x86_64 create mode 100644 netwerk/dns/IOnionAliasService.idl create mode 100644 netwerk/dns/OnionAliasService.cpp create mode 100644 netwerk/dns/OnionAliasService.h create mode 100755 run-tbb-tests create mode 100644 tbb-tests-ignore.txt create mode 100644 tbb-tests/browser.ini create mode 100644 tbb-tests/browser_tor_TB4.js create mode 100644 tbb-tests/browser_tor_bug2950.js create mode 100644 tbb-tests/browser_tor_omnibox.js create mode 100644 tbb-tests/mochitest.ini copy {browser/branding/nightly/locales => tbb-tests}/moz.build (58%) create mode 100644 tbb-tests/test_tor_bug2874.html create mode 100644 toolkit/modules/Bech32Decode.jsm create mode 100644 toolkit/modules/QRCode.jsm create mode 160000 toolkit/torproject/torbutton create mode 100644 xpcom/io/TorFileUtils.cpp create mode 100644 xpcom/io/TorFileUtils.h
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit c328863e7762160b6bd773f79a71a81837bff91f Author: Peter Van der Beken peterv@propagandism.org AuthorDate: Thu May 19 14:31:53 2022 +0000
Bug 1770137 - Make notification code use Object.create. r=Gijs,freddyb,a=dsmith
Differential Revision: https://phabricator.services.mozilla.com/D146798 --- dom/notification/old/NotificationDB.jsm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/dom/notification/old/NotificationDB.jsm b/dom/notification/old/NotificationDB.jsm index e950404f500b7..eb27b6782ff7a 100644 --- a/dom/notification/old/NotificationDB.jsm +++ b/dom/notification/old/NotificationDB.jsm @@ -40,8 +40,8 @@ var NotificationDB = { return; }
- this.notifications = {}; - this.byTag = {}; + this.notifications = Object.create(null); + this.byTag = Object.create(null); this.loaded = false;
this.tasks = []; // read/write operation queue @@ -111,7 +111,7 @@ var NotificationDB = { // populate the list of notifications by tag if (this.notifications) { for (var origin in this.notifications) { - this.byTag[origin] = {}; + this.byTag[origin] = Object.create(null); for (var id in this.notifications[origin]) { var curNotification = this.notifications[origin][id]; if (curNotification.tag) { @@ -344,8 +344,8 @@ var NotificationDB = { var origin = data.origin; var notification = data.notification; if (!this.notifications[origin]) { - this.notifications[origin] = {}; - this.byTag[origin] = {}; + this.notifications[origin] = Object.create(null); + this.byTag[origin] = Object.create(null); }
// We might have existing notification with this tag,
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 189cc425a3c8df2ccf4fb2e7562b223eb619e7c8 Author: Nika Layzell nika@thelayzells.com AuthorDate: Thu May 19 21:51:15 2022 +0000
Bug 1770137 - Part 2, r=Gijs, a=dsmith
Differential Revision: https://phabricator.services.mozilla.com/D146851 --- dom/notification/old/NotificationDB.jsm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/dom/notification/old/NotificationDB.jsm b/dom/notification/old/NotificationDB.jsm index eb27b6782ff7a..4d52c96c1aa9c 100644 --- a/dom/notification/old/NotificationDB.jsm +++ b/dom/notification/old/NotificationDB.jsm @@ -75,13 +75,14 @@ var NotificationDB = { },
filterNonAppNotifications(notifications) { + let result = Object.create(null); for (let origin in notifications) { + result[origin] = Object.create(null); let persistentNotificationCount = 0; for (let id in notifications[origin]) { if (notifications[origin][id].serviceWorkerRegistrationScope) { persistentNotificationCount++; - } else { - delete notifications[origin][id]; + result[origin][id] = notifications[origin][id]; } } if (persistentNotificationCount == 0) { @@ -90,11 +91,11 @@ var NotificationDB = { "Origin " + origin + " is not linked to an app manifest, deleting." ); } - delete notifications[origin]; + delete result[origin]; } }
- return notifications; + return result; },
// Attempt to read notification file, if it's not there we will create it.
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit b3fc1153364b2caca934e1da56fd90c934e0529f Author: Iain Ireland iireland@mozilla.com AuthorDate: Thu May 19 06:48:50 2022 +0000
Bug 1770048: Improve self-hosted new_List (ESR) r=jandem,tcampbell, a=dsmith
Differential Revision: https://phabricator.services.mozilla.com/D146774 --- js/src/builtin/Array.cpp | 32 ++++++++++++++++++++++++++++++++ js/src/builtin/Array.h | 2 ++ js/src/builtin/Module.js | 17 ++++++++--------- js/src/builtin/RegExp.js | 6 +++--- js/src/builtin/Utilities.js | 11 +---------- js/src/vm/SelfHosting.cpp | 3 ++- 6 files changed, 48 insertions(+), 23 deletions(-)
diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp index be262fb8ffebc..8afbd34fcf674 100644 --- a/js/src/builtin/Array.cpp +++ b/js/src/builtin/Array.cpp @@ -4243,3 +4243,35 @@ JS_PUBLIC_API bool JS::SetArrayLength(JSContext* cx, Handle<JSObject*> obj,
return SetLengthProperty(cx, obj, length); } + +bool js::intrinsic_newList(JSContext* cx, unsigned argc, js::Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + size_t length = 0; + gc::AllocKind allocKind = GuessArrayGCKind(length); + MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, &ArrayObject::class_)); + allocKind = ForegroundToBackgroundAllocKind(allocKind); + + /* + * Get a shape with zero fixed slots, regardless of the size class. + * See JSObject::createArray. + */ + RootedShape shape( + cx, SharedShape::getInitialShape(cx, &ArrayObject::class_, cx->realm(), + TaggedProto(), gc::AllocKind::OBJECT0)); + if (!shape) { + return false; + } + + gc::InitialHeap heap = gc::InitialHeap::DefaultHeap; + AutoSetNewObjectMetadata metadata(cx); + RootedArrayObject list(cx, ArrayObject::createArray(cx, allocKind, heap, + shape, length, metadata)); + if (!list || !AddLengthProperty(cx, list)) { + return false; + } + + args.rval().setObject(*list); + return true; +} diff --git a/js/src/builtin/Array.h b/js/src/builtin/Array.h index 2cea9fc920893..d49dca79f2474 100644 --- a/js/src/builtin/Array.h +++ b/js/src/builtin/Array.h @@ -116,6 +116,8 @@ extern bool array_slice(JSContext* cx, unsigned argc, js::Value* vp); extern JSObject* ArraySliceDense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end, HandleObject result);
+extern bool intrinsic_newList(JSContext* cx, unsigned argc, js::Value* vp); + /* * Append the given (non-hole) value to the end of an array. The array must be * a newborn array -- that is, one which has not been exposed to script for diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index 2716074452ec7..0b5cac4e030e6 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -14,7 +14,7 @@ function CallModuleResolveHook(module, moduleRequest, expectedMinimumStatus)
// https://tc39.es/ecma262/#sec-getexportednames // ES2020 15.2.1.17.2 GetExportedNames -function ModuleGetExportedNames(exportStarSet = []) +function ModuleGetExportedNames(exportStarSet = new_List()) { if (!IsObject(this) || !IsModule(this)) { return callFunction(CallModuleMethodIfWrapped, this, exportStarSet, @@ -26,13 +26,13 @@ function ModuleGetExportedNames(exportStarSet = [])
// Step 4 if (callFunction(ArrayIncludes, exportStarSet, module)) - return []; + return new_List();
// Step 5 DefineDataProperty(exportStarSet, exportStarSet.length, module);
// Step 6 - let exportedNames = []; + let exportedNames = new_List(); let namesCount = 0;
// Step 7 @@ -99,7 +99,7 @@ function ModuleSetStatus(module, newStatus) // - If the request is found to be ambiguous, the string `"ambiguous"` is // returned. // -function ModuleResolveExport(exportName, resolveSet = []) +function ModuleResolveExport(exportName, resolveSet = new_List()) { assert(typeof exportName === "string", "ModuleResolveExport");
@@ -206,7 +206,7 @@ function GetModuleNamespace(module) // Step 4 if (typeof namespace === "undefined") { let exportedNames = callFunction(module.getExportedNames, module); - let unambiguousNames = []; + let unambiguousNames = new_List(); for (let i = 0; i < exportedNames.length; i++) { let name = exportedNames[i]; let resolution = callFunction(module.resolveExport, module, name); @@ -312,7 +312,7 @@ function ModuleInstantiate() }
// Step 3 - let stack = []; + let stack = new_List();
// Steps 4-5 try { @@ -606,7 +606,7 @@ function ModuleEvaluate() }
// Step 4 - let stack = []; + let stack = new_List();
// Steps 5-6 try { @@ -769,7 +769,7 @@ function InnerModuleEvaluation(module, stack, index) }
// https://tc39.es/proposal-top-level-await/#sec-gather-async-parent-completion... -function GatherAsyncParentCompletions(module, execList = []) { +function GatherAsyncParentCompletions(module, execList = new_List()) { assert(module.status == MODULE_STATUS_EVALUATED, "bad status for async module");
// Step 5. @@ -809,4 +809,3 @@ function ExecuteAsyncModule(module) {
ExecuteModule(module); } - diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js index 81b5d6f34c119..619664288c86a 100644 --- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -349,7 +349,7 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue, }
// Step 9. - var results = []; + var results = new_List(); var nResults = 0;
// Step 11. @@ -456,7 +456,7 @@ function RegExpGetComplexReplacement(result, matched, S, position, functionalReplace, firstDollarIndex) { // Step 14.h. - var captures = []; + var captures = new_List(); var capturesLength = 0;
// Step 14.k.i (reordered). @@ -546,7 +546,7 @@ function RegExpGetFunctionalReplacement(result, S, position, replaceValue) { }
// Steps 14.g-i, 14.k.i-ii. - var captures = []; + var captures = new_List(); for (var n = 0; n <= nCaptures; n++) { assert(typeof result[n] === "string" || result[n] === undefined, "RegExpMatcher returns only strings and undefined"); diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 64ea59e4f2a2a..18400b490b6ef 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -27,16 +27,7 @@ // code are installed via the std_functions JSFunctionSpec[] in // SelfHosting.cpp.
-/********** List / Record specification types **********/ - -// A "List" is an internal type used in the ECMAScript spec to define a simple -// ordered list of values. It is never exposed to user script, but we use a -// simple Object (with null prototype) as a convenient implementation. -// -// NOTE: This does not track a `length` field. -function new_List() { - return std_Object_create(null); -} +/********** Specification types **********/
// A "Record" is an internal type used in the ECMAScript spec to define a struct diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 0246343da8d06..c90679caabdda 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2386,7 +2386,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("UnsafeSetReservedSlot", intrinsic_UnsafeSetReservedSlot, 3, 0, IntrinsicUnsafeSetReservedSlot),
- // Intrinsics and standard functions used by Intl API implementation. +// Intrinsics and standard functions used by Intl API implementation. #ifdef JS_HAS_INTL_API JS_FN("intl_BestAvailableLocale", intl_BestAvailableLocale, 3, 0), JS_FN("intl_CallCollatorMethodIfWrapped", @@ -2476,6 +2476,7 @@ static const JSFunctionSpec intrinsic_functions[] = { #endif // JS_HAS_INTL_API
// Standard builtins used by self-hosting. + JS_FN("new_List", intrinsic_newList, 0, 0), JS_INLINABLE_FN("std_Array", array_construct, 1, 0, Array), JS_INLINABLE_FN("std_Array_pop", array_pop, 0, 0, ArrayPop), JS_INLINABLE_FN("std_Array_push", array_push, 1, 0, ArrayPush),
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 514d00275d21a0979215b7f7e836115b5f6daf4c Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Tue Feb 22 18:03:02 2022 +0100
Fixed a Firefox include problem
Firefox code missed some includes, but Mozilla devs did not notice because of the unified build mechanism. They have already been fixed in repid release, but not in ESR. See https://bugzilla.mozilla.org/show_bug.cgi?id=1733356 for further information. --- netwerk/dns/TRR.cpp | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp index 2ee7110f9e2b1..4b6832af96ad7 100644 --- a/netwerk/dns/TRR.cpp +++ b/netwerk/dns/TRR.cpp @@ -6,6 +6,7 @@
#include "DNS.h" #include "DNSUtils.h" +#include "ODoH.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsHttpHandler.h" @@ -20,6 +21,7 @@ #include "nsIUploadChannel2.h" #include "nsIURIMutator.h" #include "nsNetUtil.h" +#include "nsQueryObject.h" #include "nsStringStream.h" #include "nsThreadUtils.h" #include "nsURLHelper.h"
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit bd3d62631fad7092a12de7d8f98b64e7c44b4093 Author: Georg Koppen gk@torproject.org AuthorDate: Tue Dec 7 16:19:15 2021 +0000
Revert "Bug 1724777, optimize suppressed MicroTask handling, r=mccr8 a=RyanVM"
This reverts commit 1eb1364357ac5bc2a4531337fb5416af39c3793f.
This fixes tor-browser#40721, tor-browser#40698, and tor-browser#40706. However, it is a temporary workaround, that we should revert once https://bugzilla.mozilla.org/show_bug.cgi?id=1744719 is fixed. --- dom/base/Document.cpp | 12 ------ dom/base/Document.h | 8 +++- dom/base/test/mochitest.ini | 2 - dom/base/test/test_suppressed_microtasks.html | 62 --------------------------- dom/workers/RuntimeService.cpp | 4 +- dom/workers/WorkerPrivate.cpp | 2 +- dom/worklet/WorkletThread.cpp | 2 +- xpcom/base/CycleCollectedJSContext.cpp | 51 ++++++---------------- xpcom/base/CycleCollectedJSContext.h | 29 +++---------- 9 files changed, 28 insertions(+), 144 deletions(-)
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 5be50a0ba1876..d4489a35009cd 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -15658,18 +15658,6 @@ nsAutoSyncOperation::~nsAutoSyncOperation() { } }
-void Document::SetIsInSyncOperation(bool aSync) { - if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) { - ccjs->UpdateMicroTaskSuppressionGeneration(); - } - - if (aSync) { - ++mInSyncOperationCount; - } else { - --mInSyncOperationCount; - } -} - gfxUserFontSet* Document::GetUserFontSet() { if (!mFontFaceSet) { return nullptr; diff --git a/dom/base/Document.h b/dom/base/Document.h index 7165496397f3f..69e59d09b9245 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3214,7 +3214,13 @@ class Document : public nsINode,
bool IsInSyncOperation() { return mInSyncOperationCount != 0; }
- void SetIsInSyncOperation(bool aSync); + void SetIsInSyncOperation(bool aSync) { + if (aSync) { + ++mInSyncOperationCount; + } else { + --mInSyncOperationCount; + } + }
bool CreatingStaticClone() const { return mCreatingStaticClone; }
diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 06b5691422c52..e287a0d10ae8d 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -769,8 +769,6 @@ skip-if = debug == false [test_shared_compartment2.html] [test_structuredclone_backref.html] [test_style_cssText.html] -[test_suppressed_microtasks.html] -skip-if = debug || asan || verify || toolkit == 'android' # The test needs to run reasonably fast. [test_text_wholeText.html] [test_textnode_normalize_in_selection.html] [test_textnode_split_in_selection.html] diff --git a/dom/base/test/test_suppressed_microtasks.html b/dom/base/test/test_suppressed_microtasks.html deleted file mode 100644 index f5d3336386982..0000000000000 --- a/dom/base/test/test_suppressed_microtasks.html +++ /dev/null @@ -1,62 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>Test microtask suppression</title> - <script src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> - <script> - SimpleTest.waitForExplicitFinish(); - - var previousTask = -1; - function test() { - let win = window.open("about:blank"); - win.onload = function() { - win.onmessage = function() { - win.start = win.performance.now(); - win.didRunMicrotask = false; - win.onmessage = function() { - ok(win.didRunMicrotask, "Should have run a microtask."); - let period = win.performance.now() - win.start; - win.opener.ok( - period < 200, - "Running a task should be fast. Took " + period + "ms."); - win.onmessage = null; - } - win.queueMicrotask(function() { win.didRunMicrotask = true; }); - win.postMessage("measurementMessage", "*"); - } - win.postMessage("initialMessage", "*"); - - const last = 500000; - for (let i = 0; i < last + 1; ++i) { - window.queueMicrotask(function() { - // Check that once microtasks are unsuppressed, they are handled in - // the correct order. - if (previousTask != i - 1) { - // Explicitly optimize out cases which pass. - ok(false, "Microtasks should be handled in order."); - } - previousTask = i; - if (i == last) { - win.close(); - SimpleTest.finish(); - } - }); - } - - // Synchronous XMLHttpRequest suppresses microtasks. - var xhr = new XMLHttpRequest(); - xhr.open("GET", "slow.sjs", false); - xhr.send(); - is(previousTask, -1, "Shouldn't have run microtasks during a sync XHR."); - } - } - </script> -</head> -<body onload="test()"> -<p id="display"></p> -<div id="content" style="display: none"></div> -<pre id="test"></pre> -</body> -</html> diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index c3e3f56834d72..3fda0a78fd235 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -931,7 +931,7 @@ class WorkerJSContext final : public mozilla::CycleCollectedJSContext { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(runnable);
- std::deque<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr; + std::queue<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
JSContext* cx = Context(); NS_ASSERTION(cx, "This should never be null!"); @@ -953,7 +953,7 @@ class WorkerJSContext final : public mozilla::CycleCollectedJSContext { }
JS::JobQueueMayNotBeEmpty(cx); - microTaskQueue->push_back(std::move(runnable)); + microTaskQueue->push(std::move(runnable)); }
bool IsSystemCaller() const override { diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 506ef24185c46..6907a3ae816cc 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -4313,7 +4313,7 @@ void WorkerPrivate::EnterDebuggerEventLoop() { { MutexAutoLock lock(mMutex);
- std::deque<RefPtr<MicroTaskRunnable>>& debuggerMtQueue = + std::queue<RefPtr<MicroTaskRunnable>>& debuggerMtQueue = ccjscx->GetDebuggerMicroTaskQueue(); while (mControlQueue.IsEmpty() && !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) && diff --git a/dom/worklet/WorkletThread.cpp b/dom/worklet/WorkletThread.cpp index fae1a1c550d16..c672dfb21b8b4 100644 --- a/dom/worklet/WorkletThread.cpp +++ b/dom/worklet/WorkletThread.cpp @@ -159,7 +159,7 @@ class WorkletJSContext final : public CycleCollectedJSContext { #endif
JS::JobQueueMayNotBeEmpty(cx); - GetMicroTaskQueue().push_back(std::move(runnable)); + GetMicroTaskQueue().push(std::move(runnable)); }
bool IsSystemCaller() const override { diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp index 0a35a5cf55248..347f15c82322b 100644 --- a/xpcom/base/CycleCollectedJSContext.cpp +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -61,7 +61,6 @@ CycleCollectedJSContext::CycleCollectedJSContext() mDoingStableStates(false), mTargetedMicroTaskRecursionDepth(0), mMicroTaskLevel(0), - mSuppressionGeneration(0), mDebuggerRecursionDepth(0), mMicroTaskRecursionDepth(0), mFinalizationRegistryCleanup(this) { @@ -292,7 +291,7 @@ class CycleCollectedJSContext::SavedMicroTaskQueue
private: CycleCollectedJSContext* ccjs; - std::deque<RefPtr<MicroTaskRunnable>> mQueue; + std::queue<RefPtr<MicroTaskRunnable>> mQueue; };
js::UniquePtrJS::JobQueue::SavedJobQueue @@ -380,13 +379,13 @@ void CycleCollectedJSContext::SetPendingException(Exception* aException) { mPendingException = aException; }
-std::deque<RefPtr<MicroTaskRunnable>>& +std::queue<RefPtr<MicroTaskRunnable>>& CycleCollectedJSContext::GetMicroTaskQueue() { MOZ_ASSERT(mJSContext); return mPendingMicroTaskRunnables; }
-std::deque<RefPtr<MicroTaskRunnable>>& +std::queue<RefPtr<MicroTaskRunnable>>& CycleCollectedJSContext::GetDebuggerMicroTaskQueue() { MOZ_ASSERT(mJSContext); return mDebuggerMicroTaskQueue; @@ -563,7 +562,7 @@ void CycleCollectedJSContext::DispatchToMicroTask( JS::JobQueueMayNotBeEmpty(Context());
LogMicroTaskRunnable::LogDispatch(runnable.get()); - mPendingMicroTaskRunnables.push_back(std::move(runnable)); + mPendingMicroTaskRunnables.push(std::move(runnable)); }
class AsyncMutationHandler final : public mozilla::Runnable { @@ -582,25 +581,6 @@ class AsyncMutationHandler final : public mozilla::Runnable { } };
-SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext) - : mContext(aContext), - mSuppressionGeneration(aContext->mSuppressionGeneration) {} - -bool SuppressedMicroTasks::Suppressed() { - if (mSuppressionGeneration == mContext->mSuppressionGeneration) { - return true; - } - - for (std::deque<RefPtr<MicroTaskRunnable>>::reverse_iterator it = - mSuppressedMicroTaskRunnables.rbegin(); - it != mSuppressedMicroTaskRunnables.rend(); ++it) { - mContext->GetMicroTaskQueue().push_front(*it); - } - mContext->mSuppressedMicroTasks = nullptr; - - return false; -} - bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) { AfterProcessMicrotasks(); @@ -636,14 +616,15 @@ bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { bool didProcess = false; AutoSlowOperation aso;
+ std::queue<RefPtr<MicroTaskRunnable>> suppressed; for (;;) { RefPtr<MicroTaskRunnable> runnable; if (!mDebuggerMicroTaskQueue.empty()) { runnable = std::move(mDebuggerMicroTaskQueue.front()); - mDebuggerMicroTaskQueue.pop_front(); + mDebuggerMicroTaskQueue.pop(); } else if (!mPendingMicroTaskRunnables.empty()) { runnable = std::move(mPendingMicroTaskRunnables.front()); - mPendingMicroTaskRunnables.pop_front(); + mPendingMicroTaskRunnables.pop(); } else { break; } @@ -654,16 +635,10 @@ bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly. MOZ_ASSERT(NS_IsMainThread()); JS::JobQueueMayNotBeEmpty(Context()); - if (runnable != mSuppressedMicroTasks) { - if (!mSuppressedMicroTasks) { - mSuppressedMicroTasks = new SuppressedMicroTasks(this); - } - mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back( - runnable); - } + suppressed.push(runnable); } else { if (mPendingMicroTaskRunnables.empty() && - mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) { + mDebuggerMicroTaskQueue.empty() && suppressed.empty()) { JS::JobQueueIsEmpty(Context()); } didProcess = true; @@ -678,9 +653,7 @@ bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { // Note, it is possible that we end up keeping these suppressed tasks around // for some time, but no longer than spinning the event loop nestedly // (sync XHR, alert, etc.) - if (mSuppressedMicroTasks) { - mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks); - } + mPendingMicroTaskRunnables.swap(suppressed);
AfterProcessMicrotasks();
@@ -695,7 +668,7 @@ void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() { for (;;) { // For a debugger microtask checkpoint, we always use the debugger microtask // queue. - std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue = + std::queue<RefPtr<MicroTaskRunnable>>* microtaskQueue = &GetDebuggerMicroTaskQueue();
if (microtaskQueue->empty()) { @@ -708,7 +681,7 @@ void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() { LogMicroTaskRunnable::Run log(runnable.get());
// This function can re-enter, so we remove the element before calling. - microtaskQueue->pop_front(); + microtaskQueue->pop();
if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) { JS::JobQueueIsEmpty(Context()); diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h index 116bff1c90c82..769b000418abb 100644 --- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -7,7 +7,7 @@ #ifndef mozilla_CycleCollectedJSContext_h #define mozilla_CycleCollectedJSContext_h
-#include <deque> +#include <queue>
#include "mozilla/Attributes.h" #include "mozilla/MemoryReporting.h" @@ -81,20 +81,6 @@ class MicroTaskRunnable { virtual ~MicroTaskRunnable() = default; };
-// Store the suppressed mictotasks in another microtask so that operations -// for the microtask queue as a whole keep working. -class SuppressedMicroTasks : public MicroTaskRunnable { - public: - explicit SuppressedMicroTasks(CycleCollectedJSContext* aContext); - - MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final {} - virtual bool Suppressed(); - - CycleCollectedJSContext* mContext; - uint64_t mSuppressionGeneration; - std::deque<RefPtr<MicroTaskRunnable>> mSuppressedMicroTaskRunnables; -}; - // Support for JS FinalizationRegistry objects, which allow a JS callback to be // registered that is called when objects die. // @@ -131,7 +117,6 @@ class FinalizationRegistryCleanup {
class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue { friend class CycleCollectedJSRuntime; - friend class SuppressedMicroTasks;
protected: CycleCollectedJSContext(); @@ -181,8 +166,8 @@ class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue { already_AddRefeddom::Exception GetPendingException() const; void SetPendingException(dom::Exception* aException);
- std::deque<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue(); - std::deque<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue(); + std::queue<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue(); + std::queue<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
JSContext* Context() const { MOZ_ASSERT(mJSContext); @@ -198,8 +183,6 @@ class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue { mTargetedMicroTaskRecursionDepth = aDepth; }
- void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration; } - protected: JSContext* MaybeContext() const { return mJSContext; }
@@ -333,10 +316,8 @@ class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue {
uint32_t mMicroTaskLevel;
- std::deque<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables; - std::deque<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue; - RefPtr<SuppressedMicroTasks> mSuppressedMicroTasks; - uint64_t mSuppressionGeneration; + std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables; + std::queue<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue;
// How many times the debugger has interrupted execution, possibly creating // microtask checkpoints in places that they would not normally occur.
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 03abec7978becdcd40f64686a2eb2e165eb21e92 Author: Gaba gaba@torproject.org AuthorDate: Mon Jun 28 11:44:16 2021 -0700
Adding issue template for bugs. --- .gitlab/issue_templates/UXBug.md | 29 +++++++++++++++++++++++++++++ .gitlab/issue_templates/bug.md | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+)
diff --git a/.gitlab/issue_templates/UXBug.md b/.gitlab/issue_templates/UXBug.md new file mode 100644 index 0000000000000..8e7cb2a5e1633 --- /dev/null +++ b/.gitlab/issue_templates/UXBug.md @@ -0,0 +1,29 @@ +<!-- +* Use this issue template for reporting a new UX bug. +--> + +### Summary +**Summarize the bug encountered concisely.** + + +### Steps to reproduce: +**How one can reproduce the issue - this is very important.** + +1. Step 1 +2. Step 2 +3. ... + +### What is the current bug behavior? +**What actually happens.** + + +### What is the expected behavior? +**What you want to see instead** + + + +## Relevant logs and/or screenshots +**Do you have screenshots? Attach them to this ticket please.** + +/label ~tor-ux ~needs-investigation ~bug +/assign @nah diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md new file mode 100644 index 0000000000000..6ce85a4864be8 --- /dev/null +++ b/.gitlab/issue_templates/bug.md @@ -0,0 +1,32 @@ +<!-- +* Use this issue template for reporting a new bug. +--> + +### Summary +**Summarize the bug encountered concisely.** + + +### Steps to reproduce: +**How one can reproduce the issue - this is very important.** + +1. Step 1 +2. Step 2 +3. ... + +### What is the current bug behavior? +**What actually happens.** + + +### What is the expected behavior? +**What you want to see instead** + + + +### Environment +**Which operating system are you using? For example: Debian GNU/Linux 10.1, Windows 10, Ubuntu Xenial, FreeBSD 12.2, etc.** +**Which installation method did you use? Distribution package (apt, pkg, homebrew), from source tarball, from Git, etc.** + +### Relevant logs and/or screenshots + + +/label ~bug
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 62b5438836fcad8e3167c1ac9547afa5be3e5fbe Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon May 23 17:01:24 2022 +0200
Base Browser's .mozconfigs. --- browser/config/mozconfigs/base-browser | 31 +++++++++++++++++++++++++++++++ mozconfig-linux-i686 | 9 +++++++++ mozconfig-linux-x86_64 | 9 +++++++++ mozconfig-linux-x86_64-asan | 22 ++++++++++++++++++++++ mozconfig-linux-x86_64-dev | 10 ++++++++++ mozconfig-macos-x86_64 | 3 +++ mozconfig-windows-i686 | 11 +++++++++++ mozconfig-windows-x86_64 | 11 +++++++++++ 8 files changed, 106 insertions(+)
diff --git a/browser/config/mozconfigs/base-browser b/browser/config/mozconfigs/base-browser new file mode 100644 index 0000000000000..eb495fd0df948 --- /dev/null +++ b/browser/config/mozconfigs/base-browser @@ -0,0 +1,31 @@ +# Shared build settings and settings to enhance security and privacy. + +. $topsrcdir/browser/config/mozconfig + +if test -f "$topsrcdir/mozconfig-toolchain"; then + . $topsrcdir/mozconfig-toolchain +fi + +mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-@CONFIG_GUESS@ + +ac_add_options --enable-official-branding +export MOZILLA_OFFICIAL=1 + +ac_add_options --enable-optimize +ac_add_options --enable-rust-simd + +ac_add_options --enable-bundled-fonts + +ac_add_options --disable-tests +ac_add_options --disable-debug + +ac_add_options --disable-crashreporter +# Before removing, please notice that WebRTC does not work on mingw (Bug 1393901) +ac_add_options --disable-webrtc +ac_add_options --disable-parental-controls +# Let's make sure no preference is enabling either Adobe's or Google's CDM. +ac_add_options --disable-eme +ac_add_options --enable-proxy-bypass-protection + +# Disable telemetry +ac_add_options MOZ_TELEMETRY_REPORTING= diff --git a/mozconfig-linux-i686 b/mozconfig-linux-i686 new file mode 100644 index 0000000000000..16a75279e665a --- /dev/null +++ b/mozconfig-linux-i686 @@ -0,0 +1,9 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +ac_add_options --target=i686-linux-gnu + +ac_add_options --enable-default-toolkit=cairo-gtk3 + +# Bug 31448: ld.gold fails if we don't disable debug-symbols. +# Also, we keep strip enabled. +ac_add_options --disable-debug-symbols diff --git a/mozconfig-linux-x86_64 b/mozconfig-linux-x86_64 new file mode 100644 index 0000000000000..f14f1f4df86b3 --- /dev/null +++ b/mozconfig-linux-x86_64 @@ -0,0 +1,9 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +ac_add_options --enable-default-toolkit=cairo-gtk3 + +ac_add_options --disable-strip +ac_add_options --disable-install-strip + +# We want to bundle an own geckodriver, so we can use it for QA and other work +ac_add_options --enable-geckodriver diff --git a/mozconfig-linux-x86_64-asan b/mozconfig-linux-x86_64-asan new file mode 100644 index 0000000000000..62ca3feaad992 --- /dev/null +++ b/mozconfig-linux-x86_64-asan @@ -0,0 +1,22 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +export CFLAGS="-fsanitize=address -Dxmalloc=myxmalloc" +export CXXFLAGS="-fsanitize=address -Dxmalloc=myxmalloc" +# We need to add -ldl explicitely due to bug 1213698 +export LDFLAGS="-fsanitize=address -ldl" + +# Define HOST_CFLAGS, etc. to avoid compiling programs such as mbsdiff +# (which is part of mar-tools and is not distributed to end-users) with +# ASan. See bug 17858. +export HOST_CFLAGS="" +export HOST_CXXFLAGS="" +export HOST_LDFLAGS="-ldl" + +ac_add_options --enable-address-sanitizer +ac_add_options --disable-jemalloc +ac_add_options --disable-elf-hack + +ac_add_options --enable-default-toolkit=cairo-gtk3 + +ac_add_options --disable-strip +ac_add_options --disable-install-strip diff --git a/mozconfig-linux-x86_64-dev b/mozconfig-linux-x86_64-dev new file mode 100644 index 0000000000000..1e68b1c85103f --- /dev/null +++ b/mozconfig-linux-x86_64-dev @@ -0,0 +1,10 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +# This mozconfig file is not used in official builds. +# It is only intended to be used when doing incremental Linux builds +# during development. + +ac_add_options --enable-default-toolkit=cairo-gtk3 + +ac_add_options --disable-strip +ac_add_options --disable-install-strip diff --git a/mozconfig-macos-x86_64 b/mozconfig-macos-x86_64 new file mode 100644 index 0000000000000..0fbda17fa63cd --- /dev/null +++ b/mozconfig-macos-x86_64 @@ -0,0 +1,3 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +ac_add_options --enable-strip diff --git a/mozconfig-windows-i686 b/mozconfig-windows-i686 new file mode 100644 index 0000000000000..1b9997d01e8e4 --- /dev/null +++ b/mozconfig-windows-i686 @@ -0,0 +1,11 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +ac_add_options --target=i686-w64-mingw32 +ac_add_options --with-toolchain-prefix=i686-w64-mingw32- + +ac_add_options --enable-strip + +# Bits is Background Intelligent Transfer Service +ac_add_options --disable-bits-download +ac_add_options --disable-maintenance-service +ac_add_options --disable-default-browser-agent diff --git a/mozconfig-windows-x86_64 b/mozconfig-windows-x86_64 new file mode 100644 index 0000000000000..e65ccce89db71 --- /dev/null +++ b/mozconfig-windows-x86_64 @@ -0,0 +1,11 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +ac_add_options --target=x86_64-w64-mingw32 +ac_add_options --with-toolchain-prefix=x86_64-w64-mingw32- + +ac_add_options --enable-strip + +# Bits is Background Intelligent Transfer Service +ac_add_options --disable-bits-download +ac_add_options --disable-maintenance-service +ac_add_options --disable-default-browser-agent
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 8744faeafd6a443b63593f3f827cc7a2128e5530 Author: Matthew Finkel Matthew.Finkel@gmail.com AuthorDate: Wed Apr 11 17:52:59 2018 +0000
Bug 24796 - Comment out excess permissions from GeckoView
The GeckoView AndroidManifest.xml is not preprocessed unlike Fennec's manifest, so we can't use the ifdef preprocessor guards around the permissions we do not want. Commenting the permissions is the next-best-thing. --- .../android/geckoview/src/main/AndroidManifest.xml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/mobile/android/geckoview/src/main/AndroidManifest.xml b/mobile/android/geckoview/src/main/AndroidManifest.xml index a76b6a4754b66..7a2f30708fc3c 100644 --- a/mobile/android/geckoview/src/main/AndroidManifest.xml +++ b/mobile/android/geckoview/src/main/AndroidManifest.xml @@ -6,20 +6,32 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.mozilla.geckoview">
+<!--#ifdef MOZ_ANDROID_NETWORK_STATE--> + <!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + --> +<!--#endif--> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+<!--#ifdef MOZ_ANDROID_LOCATION--> + <!-- <uses-feature android:name="android.hardware.location" android:required="false"/> <uses-feature android:name="android.hardware.location.gps" android:required="false"/> + --> +<!--#endif--> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> +<!--#ifdef MOZ_WEBRTC--> + <!-- TODO preprocess AndroidManifest.xml so that we can + conditionally include WebRTC permissions based on MOZ_WEBRTC. --> + <!-- <uses-feature android:name="android.hardware.camera" android:required="false"/> @@ -28,14 +40,16 @@ android:required="false"/>
<uses-feature - android:name="android.hardware.audio.low_latency" + android:name="android.hardware.camera.any" android:required="false"/> <uses-feature - android:name="android.hardware.microphone" + android:name="android.hardware.audio.low_latency" android:required="false"/> <uses-feature - android:name="android.hardware.camera.any" + android:name="android.hardware.microphone" android:required="false"/> + --> +<!--#endif-->
<!-- GeckoView requires OpenGL ES 2.0 --> <uses-feature
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 95b11cbd2456665c59874d272bb76f74105085f8 Author: Matthew Finkel Matthew.Finkel@gmail.com AuthorDate: Thu Apr 26 22:22:51 2018 +0000
Bug 25741 - TBA: Disable GeckoNetworkManager
The browser should not need information related to the network interface or network state, tor should take care of that. --- .../src/main/java/org/mozilla/geckoview/GeckoRuntime.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index f084b522ad533..b94d8e803b6b3 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -122,7 +122,9 @@ public final class GeckoRuntime implements Parcelable { mPaused = false; // Monitor network status and send change notifications to Gecko // while active. - GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext()); + if (BuildConfig.TOR_BROWSER_VERSION == "") { + GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext()); + } }
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -130,7 +132,9 @@ public final class GeckoRuntime implements Parcelable { Log.d(LOGTAG, "Lifecycle: onPause"); mPaused = true; // Stop monitoring network status while inactive. - GeckoNetworkManager.getInstance().stop(); + if (BuildConfig.TOR_BROWSER_VERSION == "") { + GeckoNetworkManager.getInstance().stop(); + } GeckoThread.onPause(); } }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 0069499a6abb5e6e972c45cc04478283178fc128 Author: Matthew Finkel Matthew.Finkel@gmail.com AuthorDate: Thu Oct 25 19:17:09 2018 +0000
Bug 28125 - Prevent non-Necko network connections --- .../gecko/media/GeckoMediaDrmBridgeV21.java | 49 +--------------------- .../exoplayer2/upstream/DefaultHttpDataSource.java | 47 ++------------------- 2 files changed, 4 insertions(+), 92 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java index 6e75e6fb4dd50..7faa9bc2821db 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java @@ -490,54 +490,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
@Override protected Void doInBackground(final Void... params) { - HttpURLConnection urlConnection = null; - BufferedReader in = null; - try { - final URI finalURI = new URI(mURL + "&signedRequest=" + URLEncoder.encode(new String(mDrmRequest), "UTF-8")); - urlConnection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(finalURI); - urlConnection.setRequestMethod("POST"); - if (DEBUG) Log.d(LOGTAG, "Provisioning, posting url =" + finalURI.toString()); - - // Add data - urlConnection.setRequestProperty("Accept", "*/*"); - urlConnection.setRequestProperty("User-Agent", getCDMUserAgent()); - urlConnection.setRequestProperty("Content-Type", "application/json"); - - // Execute HTTP Post Request - urlConnection.connect(); - - final int responseCode = urlConnection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), UTF_8)); - String inputLine; - final StringBuffer response = new StringBuffer(); - - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - in.close(); - mResponseBody = String.valueOf(response).getBytes(UTF_8); - if (DEBUG) Log.d(LOGTAG, "Provisioning, response received."); - if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length); - } else { - Log.d(LOGTAG, "Provisioning, server returned HTTP error code :" + responseCode); - } - } catch (final IOException e) { - Log.e(LOGTAG, "Got exception during posting provisioning request ...", e); - } catch (final URISyntaxException e) { - Log.e(LOGTAG, "Got exception during creating uri ...", e); - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - try { - if (in != null) { - in.close(); - } - } catch (final IOException e) { - Log.e(LOGTAG, "Exception during closing in ...", e); - } - } + Log.i(LOGTAG, "This is Tor Browser. Skipping."); return null; }
diff --git a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 6e5095b0a4c92..a585e283ed4e3 100644 --- a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -46,6 +46,7 @@ import java.util.regex.Pattern; import java.util.zip.GZIPInputStream;
import org.mozilla.gecko.util.ProxySelector; + /** * An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. * @@ -516,50 +517,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou boolean followRedirects, Map<String, String> requestParameters) throws IOException, URISyntaxException { - /** - * Tor Project modified the way the connection object was created. For the sake of - * simplicity, instead of duplicating the whole file we changed the connection object - * to use the ProxySelector. - */ - HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI()); - - connection.setConnectTimeout(connectTimeoutMillis); - connection.setReadTimeout(readTimeoutMillis); - - Map<String, String> requestHeaders = new HashMap<>(); - if (defaultRequestProperties != null) { - requestHeaders.putAll(defaultRequestProperties.getSnapshot()); - } - requestHeaders.putAll(requestProperties.getSnapshot()); - requestHeaders.putAll(requestParameters); - - for (Map.Entry<String, String> property : requestHeaders.entrySet()) { - connection.setRequestProperty(property.getKey(), property.getValue()); - } - - if (!(position == 0 && length == C.LENGTH_UNSET)) { - String rangeRequest = "bytes=" + position + "-"; - if (length != C.LENGTH_UNSET) { - rangeRequest += (position + length - 1); - } - connection.setRequestProperty("Range", rangeRequest); - } - connection.setRequestProperty("User-Agent", userAgent); - connection.setRequestProperty("Accept-Encoding", allowGzip ? "gzip" : "identity"); - connection.setInstanceFollowRedirects(followRedirects); - connection.setDoOutput(httpBody != null); - connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod)); - - if (httpBody != null) { - connection.setFixedLengthStreamingMode(httpBody.length); - connection.connect(); - OutputStream os = connection.getOutputStream(); - os.write(httpBody); - os.close(); - } else { - connection.connect(); - } - return connection; + Log.i(TAG, "This is Tor Browser. Skipping."); + throw new IOException(); }
/** Creates an {@link HttpURLConnection} that is connected with the {@code url}. */
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 43b312a25a3c50627019deb39831adc628c1f549 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Wed Aug 27 15:19:10 2014 -0700
Bug 12974: Disable NTLM and Negotiate HTTP Auth
This is technically an embargoed Mozilla bug, so I probably shouldn't provide too many details.
Suffice to say that NTLM and Negotiate auth are bad for Tor users, and I doubt very many (or any of them) actually need it.
The Mozilla bug was https://bugzilla.mozilla.org/show_bug.cgi?id=1046421, however we still need to keep this patch because of tor-browser#27602 (which refers https://bugzilla.mozilla.org/show_bug.cgi?id=1261591) --- extensions/auth/nsHttpNegotiateAuth.cpp | 4 ++++ netwerk/protocol/http/nsHttpNTLMAuth.cpp | 3 +++ 2 files changed, 7 insertions(+)
diff --git a/extensions/auth/nsHttpNegotiateAuth.cpp b/extensions/auth/nsHttpNegotiateAuth.cpp index 496c0b0f8292b..e37d0bc982684 100644 --- a/extensions/auth/nsHttpNegotiateAuth.cpp +++ b/extensions/auth/nsHttpNegotiateAuth.cpp @@ -155,6 +155,10 @@ nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel, nsIAuthModule* rawModule = (nsIAuthModule*)*continuationState;
*identityInvalid = false; + + /* Always fail Negotiate auth for Tor Browser. We don't need it. */ + return NS_ERROR_ABORT; + if (rawModule) { return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.cpp b/netwerk/protocol/http/nsHttpNTLMAuth.cpp index a98093b484faa..e44fc4153e2e7 100644 --- a/netwerk/protocol/http/nsHttpNTLMAuth.cpp +++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp @@ -169,6 +169,9 @@ nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel* channel,
*identityInvalid = false;
+ /* Always fail Negotiate auth for Tor Browser. We don't need it. */ + return NS_ERROR_ABORT; + // Start a new auth sequence if the challenge is exactly "NTLM". // If native NTLM auth apis are available and enabled through prefs, // try to use them.
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 5eb63a4a77f4d3155f713c4d04e3f476a41549ee Author: Alex Catarineu acat@torproject.org AuthorDate: Fri Oct 9 12:55:35 2020 +0200
Bug 40166: Disable security.certerrors.mitm.auto_enable_enterprise_roots
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1768899 --- browser/components/BrowserGlue.jsm | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index d23d72a1afc88..0b6a4506b80d6 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -1352,6 +1352,20 @@ BrowserGlue.prototype = { // handle any UI migration this._migrateUI();
+ // Clear possibly auto enabled enterprise_roots prefs (see bug 40166) + if ( + !Services.prefs.getBoolPref( + "security.certerrors.mitm.auto_enable_enterprise_roots" + ) && + Services.prefs.getBoolPref( + "security.enterprise_roots.auto-enabled", + false + ) + ) { + Services.prefs.clearUserPref("security.enterprise_roots.enabled"); + Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled"); + } + if (!Services.prefs.prefHasUserValue(PREF_PDFJS_ISDEFAULT_CACHE_STATE)) { PdfJs.checkIsDefault(this._isNewProfile); }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 08badffdb0ef40bc1941d1066a5e2fe6c7d50749 Author: Georg Koppen gk@torproject.org AuthorDate: Mon May 22 12:44:40 2017 +0000
Bug 16285: Exclude ClearKey system for now
In the past the ClearKey system had not been compiled when specifying --disable-eme. But that changed and it is even bundled nowadays (see: Mozilla's bug 1300654). We don't want to ship it right now as the use case for it is not really visible while the code had security vulnerabilities in the past. --- browser/installer/package-manifest.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 40de7394e6eec..c64d488c750c5 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -449,8 +449,8 @@ bin/libfreebl_64int_3.so #endif
; media -@RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@ -@RESPATH@/gmp-clearkey/0.1/manifest.json +;@RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@ +;@RESPATH@/gmp-clearkey/0.1/manifest.json
#ifdef MOZ_DMD ; DMD
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 05030ce98b979d2a75f817741409e9462c8fc7af Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue May 23 17:05:29 2017 -0400
Bug 21431: Clean-up system extensions shipped in Firefox
Only ship the pdfjs extension. --- browser/components/BrowserGlue.jsm | 6 ++++++ browser/extensions/moz.build | 10 +--------- browser/installer/package-manifest.in | 1 - browser/locales/Makefile.in | 8 -------- browser/locales/jar.mn | 7 ------- 5 files changed, 7 insertions(+), 25 deletions(-)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 0b6a4506b80d6..5ebd1cd6515a7 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -1966,6 +1966,9 @@ BrowserGlue.prototype = { const ID = "screenshots@mozilla.org"; const _checkScreenshotsPref = async () => { let addon = await AddonManager.getAddonByID(ID); + if (!addon) { + return; + } let disabled = Services.prefs.getBoolPref(PREF, false); if (disabled) { await addon.disable({ allowSystemAddons: true }); @@ -1982,6 +1985,9 @@ BrowserGlue.prototype = { const ID = "webcompat-reporter@mozilla.org"; Services.prefs.addObserver(PREF, async () => { let addon = await AddonManager.getAddonByID(ID); + if (!addon) { + return; + } let enabled = Services.prefs.getBoolPref(PREF, false); if (enabled && !addon.isActive) { await addon.enable({ allowSystemAddons: true }); diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build index 26d059a21aef6..ab735cf2688f4 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -4,15 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DIRS += [ - "doh-rollout", - "formautofill", - "screenshots", - "webcompat", - "report-site-issue", - "pictureinpicture", - "proxy-failover", -] +DIRS += []
if CONFIG["NIGHTLY_BUILD"]: DIRS += [ diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index c64d488c750c5..0c9051642686b 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -250,7 +250,6 @@ @RESPATH@/browser/chrome/icons/default/default64.png @RESPATH@/browser/chrome/icons/default/default128.png #endif -@RESPATH@/browser/features/*
; [DevTools Startup Files] @RESPATH@/browser/chrome/devtools-startup@JAREXT@ diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index 496379c4306fe..0946188813da5 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -58,10 +58,6 @@ l10n-%: @$(MAKE) -C ../../toolkit/locales l10n-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)' @$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$* @$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$* -ifneq (,$(wildcard ../extensions/formautofill/locales)) - @$(MAKE) -C ../extensions/formautofill/locales AB_CD=$* XPI_NAME=locale-$* -endif - @$(MAKE) -C ../extensions/report-site-issue/locales AB_CD=$* XPI_NAME=locale-$* @$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)' @$(MAKE) -C ../../devtools/startup/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)' @$(MAKE) l10n AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR) @@ -75,14 +71,10 @@ chrome-%: @$(MAKE) -C ../../toolkit/locales chrome-$* @$(MAKE) -C ../../services/sync/locales chrome AB_CD=$* @$(MAKE) -C ../../extensions/spellcheck/locales chrome AB_CD=$* -ifneq (,$(wildcard ../extensions/formautofill/locales)) - @$(MAKE) -C ../extensions/formautofill/locales chrome AB_CD=$* -endif @$(MAKE) -C ../../devtools/client/locales chrome AB_CD=$* @$(MAKE) -C ../../devtools/startup/locales chrome AB_CD=$* @$(MAKE) chrome AB_CD=$* @$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales chrome AB_CD=$* - @$(MAKE) -C ../extensions/report-site-issue/locales chrome AB_CD=$*
package-win32-installer: $(SUBMAKEFILES) $(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen ZIP_IN='$(ZIP_OUT)' installer diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn index 3b7963c854e13..fd6e0ac768436 100644 --- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -45,10 +45,3 @@ # the following files are browser-specific overrides locale/browser/netError.dtd (%chrome/overrides/netError.dtd) locale/browser/appstrings.properties (%chrome/overrides/appstrings.properties) - -#ifdef XPI_NAME -# Bug 1240628, restructure how l10n repacks work with feature addons -# This is hacky, but ensures the chrome.manifest chain is complete -[.] chrome.jar: -% manifest features/chrome.manifest -#endif
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 55c1bd7df21fb0e4f9d55fdea9a25e7406401df9 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue Jul 14 11:15:07 2020 -0400
Bug 33852: Clean up about:logins (LockWise) to avoid mentioning sync, etc.
Hide elements on about:logins that mention sync, "Firefox LockWise", and Mozilla's LockWise mobile apps.
Disable the "Create New Login" button when security.nocertdb is true. --- browser/components/aboutlogins/AboutLoginsParent.jsm | 2 ++ browser/components/aboutlogins/content/aboutLogins.css | 8 +++++++- browser/components/aboutlogins/content/aboutLogins.js | 6 ++++++ .../aboutlogins/content/components/fxaccounts-button.css | 5 +++++ .../components/aboutlogins/content/components/menu-button.css | 10 ++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/browser/components/aboutlogins/AboutLoginsParent.jsm b/browser/components/aboutlogins/AboutLoginsParent.jsm index db0b55d26abc4..39fd2356ce992 100644 --- a/browser/components/aboutlogins/AboutLoginsParent.jsm +++ b/browser/components/aboutlogins/AboutLoginsParent.jsm @@ -61,6 +61,7 @@ XPCOMUtils.defineLazyGetter(this, "AboutLoginsL10n", () => {
const ABOUT_LOGINS_ORIGIN = "about:logins"; const MASTER_PASSWORD_NOTIFICATION_ID = "master-password-login-required"; +const NOCERTDB_PREF = "security.nocertdb";
// about:logins will always use the privileged content process, // even if it is disabled for other consumers such as about:newtab. @@ -273,6 +274,7 @@ class AboutLoginsParent extends JSWindowActorParent { importVisible: Services.policies.isAllowed("profileImport") && AppConstants.platform != "linux", + canCreateLogins: !Services.prefs.getBoolPref(NOCERTDB_PREF, false), });
await AboutLogins._sendAllLoginRelatedObjects( diff --git a/browser/components/aboutlogins/content/aboutLogins.css b/browser/components/aboutlogins/content/aboutLogins.css index e3528ca49b84d..cc56c7148fcf7 100644 --- a/browser/components/aboutlogins/content/aboutLogins.css +++ b/browser/components/aboutlogins/content/aboutLogins.css @@ -69,6 +69,11 @@ login-item { grid-area: login; }
+/* Do not promote Mozilla Sync. */ +login-intro { + display: none !important; +} + #branding-logo { flex-basis: var(--sidebar-width); flex-shrink: 0; @@ -83,7 +88,8 @@ login-item { } }
-:root:not(.official-branding) #branding-logo { +/* Hide "Firefox LockWise" branding. */ +#branding-logo { visibility: hidden; }
diff --git a/browser/components/aboutlogins/content/aboutLogins.js b/browser/components/aboutlogins/content/aboutLogins.js index 494ef5c7a15b3..27ff0295f2f64 100644 --- a/browser/components/aboutlogins/content/aboutLogins.js +++ b/browser/components/aboutlogins/content/aboutLogins.js @@ -22,6 +22,9 @@ const gElements = { ".menuitem-remove-all-logins" ); }, + get createNewLoginButton() { + return this.loginList.shadowRoot.querySelector(".create-login-button"); + }, };
let numberOfLogins = 0; @@ -128,6 +131,9 @@ window.addEventListener("AboutLoginsChromeToContent", event => { gElements.loginList.setSortDirection(event.detail.value.selectedSort); document.documentElement.classList.add("initialized"); gElements.loginList.classList.add("initialized"); + if (!event.detail.value.canCreateLogins) { + gElements.createNewLoginButton.disabled = true; + } break; } case "ShowLoginItemError": { diff --git a/browser/components/aboutlogins/content/components/fxaccounts-button.css b/browser/components/aboutlogins/content/components/fxaccounts-button.css index c8925f6fc75d1..3703c5ab25ea8 100644 --- a/browser/components/aboutlogins/content/components/fxaccounts-button.css +++ b/browser/components/aboutlogins/content/components/fxaccounts-button.css @@ -8,6 +8,11 @@ align-items: center; }
+/* Do not promote Mozilla Sync. */ +.logged-out-view { + display: none !important; +} + .fxaccounts-extra-text { /* Only show at most 3 lines of text to limit the text from overflowing the header. */ diff --git a/browser/components/aboutlogins/content/components/menu-button.css b/browser/components/aboutlogins/content/components/menu-button.css index 99ca6a7110939..b1508b3cf3c08 100644 --- a/browser/components/aboutlogins/content/components/menu-button.css +++ b/browser/components/aboutlogins/content/components/menu-button.css @@ -92,3 +92,13 @@ .menuitem-preferences { background-image: url("chrome://global/skin/icons/settings.svg"); } + +/* + * Do not promote LockWise mobile apps: hide the menu items and the separator + * line that precedes them. + */ +.menuitem-mobile-android, +.menuitem-mobile-ios, +button[data-event-name="AboutLoginsGetHelp"] + hr { + display: none !important; +}
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 3d165743e99319833795e6619f45234ea12558bb Author: Alex Catarineu acat@torproject.org AuthorDate: Mon Jul 27 18:12:55 2020 +0200
Bug 40025: Remove Mozilla add-on install permissions --- browser/app/permissions | 5 ----- 1 file changed, 5 deletions(-)
diff --git a/browser/app/permissions b/browser/app/permissions index c33d0155a15e7..47eccccec5a13 100644 --- a/browser/app/permissions +++ b/browser/app/permissions @@ -16,11 +16,6 @@ origin uitour 1 https://support.mozilla.org origin uitour 1 about:home origin uitour 1 about:newtab
-# XPInstall -origin install 1 https://addons.mozilla.org - # Remote troubleshooting origin remote-troubleshooting 1 https://support.mozilla.org
-# addon install -origin install 1 https://fpn.firefox.com
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 444644b403b58d619d92d2195832c57d8649fd83 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Fri Aug 14 09:06:33 2020 -0400
Bug 40002: Remove about:ion
Firefox Ion (previously Firefox Pioneer) is an opt-in program in which people volunteer to participate in studies that collect detailed, sensitive data about how they use their browser. --- browser/base/content/browser-siteIdentity.js | 2 +- browser/components/about/AboutRedirector.cpp | 2 -- browser/components/about/components.conf | 1 - 3 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 859ebf5eaa3f3..8c6d1e20ddef7 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -57,7 +57,7 @@ var gIdentityHandler = { * RegExp used to decide if an about url should be shown as being part of * the browser UI. */ - _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|ion)(?:[?#]|$)/i, + _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback)(?:[?#]|$)/i,
/** * Whether the established HTTPS connection is considered "broken". diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 5412a65c315fe..2ace276cd50c1 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -126,8 +126,6 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, - {"ion", "chrome://browser/content/ion.html", - nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 0c9597ff9fb4b..6fd827dead373 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -13,7 +13,6 @@ pages = [ 'logins', 'loginsimportreport', 'newtab', - 'ion', 'pocket-home', 'pocket-saved', 'pocket-signup',
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 7160d9dfe275c9bded441566430de7fbb1c07864 Author: Georg Koppen gk@torproject.org AuthorDate: Wed Apr 20 14:34:50 2016 +0000
Bug 18821: Disable libmdns for Android and Desktop
There should be no need to remove the OS X support introduced in https://bugzilla.mozilla.org/show_bug.cgi?id=1225726 as enabling this is governed by a preference (which is actually set to `false`). However, we remove it at build time as well (defense in depth).
This is basically a backout of the relevant passages of https://hg.mozilla.org/mozilla-central/rev/6bfb430de85d, https://hg.mozilla.org/mozilla-central/rev/609b337bf7ab and https://hg.mozilla.org/mozilla-central/rev/8e092ec5fbbd.
Fixed bug 21861 (Disable additional mDNS code to avoid proxy bypasses) as well.
Mozilla removed the Presentation API piece of this patch in Bug 1697680. --- netwerk/dns/mdns/libmdns/components.conf | 15 --------------- netwerk/dns/mdns/libmdns/moz.build | 28 ---------------------------- 2 files changed, 43 deletions(-)
diff --git a/netwerk/dns/mdns/libmdns/components.conf b/netwerk/dns/mdns/libmdns/components.conf index 6e64140c820ed..1b50dbf673a4a 100644 --- a/netwerk/dns/mdns/libmdns/components.conf +++ b/netwerk/dns/mdns/libmdns/components.conf @@ -5,20 +5,5 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
Classes = [ - { - 'cid': '{14a50f2b-7ff6-48a5-88e3-615fd111f5d3}', - 'contract_ids': ['@mozilla.org/toolkit/components/mdnsresponder/dns-info;1'], - 'type': 'mozilla::net::nsDNSServiceInfo', - 'headers': ['/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h'], - }, ]
-if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] != 'cocoa': - Classes += [ - { - 'cid': '{f9346d98-f27a-4e89-b744-493843416480}', - 'contract_ids': ['@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1'], - 'jsm': 'resource://gre/modules/DNSServiceDiscovery.jsm', - 'constructor': 'nsDNSServiceDiscovery', - }, - ] diff --git a/netwerk/dns/mdns/libmdns/moz.build b/netwerk/dns/mdns/libmdns/moz.build index f9c025fa823ee..e6e70a6d803c5 100644 --- a/netwerk/dns/mdns/libmdns/moz.build +++ b/netwerk/dns/mdns/libmdns/moz.build @@ -4,34 +4,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": - UNIFIED_SOURCES += [ - "MDNSResponderOperator.cpp", - "MDNSResponderReply.cpp", - "nsDNSServiceDiscovery.cpp", - ] - - LOCAL_INCLUDES += [ - "/netwerk/base", - ] - -else: - EXTRA_JS_MODULES += [ - "DNSServiceDiscovery.jsm", - "fallback/DataReader.jsm", - "fallback/DataWriter.jsm", - "fallback/DNSPacket.jsm", - "fallback/DNSRecord.jsm", - "fallback/DNSResourceRecord.jsm", - "fallback/DNSTypes.jsm", - "fallback/MulticastDNS.jsm", - ] - - if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": - EXTRA_JS_MODULES += [ - "MulticastDNSAndroid.jsm", - ] - UNIFIED_SOURCES += [ "nsDNSServiceInfo.cpp", ]
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 5eac23294daf4905893d360a2b3efc1613878059 Author: Arthur Edelstein arthuredelstein@gmail.com AuthorDate: Sat Jul 14 08:50:55 2018 -0700
Bug 26353: Prevent speculative connect that violated FPI.
Connections were observed in the catch-all circuit when the user entered an https or http URL in the URL bar, or typed a search term. --- toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm index 5d46b1dd8e3b2..c2fea54d0744d 100644 --- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm +++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm @@ -95,6 +95,9 @@ class RemoteWebNavigation { }
uri = Services.uriFixup.getFixupURIInfo(aURI, fixupFlags).preferredURI; +/******************************************************************************* + Disable the following speculative connect until + we can make it properly obey first-party isolation.
// We know the url is going to be loaded, let's start requesting network // connection before the content process asks. @@ -118,6 +121,7 @@ class RemoteWebNavigation { } Services.io.speculativeConnect(uri, principal, null); } +*******************************************************************************/ } catch (ex) { // Can't setup speculative connection for this uri string for some // reason (such as failing to parse the URI), just ignore it.
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 7ecef3c2e4e018485d5affdbfc4819b9ebdd660b Author: Alex Catarineu acat@torproject.org AuthorDate: Wed Oct 16 23:01:12 2019 +0200
Bug 31740: Remove some unnecessary RemoteSettings instances
More concretely, SearchService.jsm 'hijack-blocklists' and url-classifier-skip-urls.
Avoid creating instance for 'anti-tracking-url-decoration'.
If prefs are disabling their usage, avoid creating instances for 'cert-revocations' and 'intermediates'.
Do not ship JSON dumps for collections we do not expect to need. For the ones in the 'main' bucket, this prevents them from being synced unnecessarily (the code in remote-settings does so for collections in the main bucket for which a dump or local data exists). For the collections in the other buckets, we just save some size by not shipping their dumps.
We also clear the collections database on the v2 -> v3 migration. --- browser/components/search/SearchSERPTelemetry.jsm | 6 ------ .../url-classifier/UrlClassifierFeatureBase.cpp | 2 +- netwerk/url-classifier/components.conf | 6 ------ security/manager/ssl/RemoteSecuritySettings.jsm | 23 ++++++++++++++++++++++ services/settings/IDBHelpers.jsm | 4 ++++ services/settings/dumps/blocklists/moz.build | 14 +++++-------- services/settings/dumps/main/moz.build | 7 ------- services/settings/dumps/security-state/moz.build | 1 - .../components/antitracking/antitracking.manifest | 2 +- toolkit/components/antitracking/components.conf | 7 ------- toolkit/components/search/SearchService.jsm | 2 -- 11 files changed, 34 insertions(+), 40 deletions(-)
diff --git a/browser/components/search/SearchSERPTelemetry.jsm b/browser/components/search/SearchSERPTelemetry.jsm index 2a99137d118b6..8df738e4372f7 100644 --- a/browser/components/search/SearchSERPTelemetry.jsm +++ b/browser/components/search/SearchSERPTelemetry.jsm @@ -96,13 +96,7 @@ class TelemetryHandler { return; }
- this._telemetrySettings = RemoteSettings(TELEMETRY_SETTINGS_KEY); let rawProviderInfo = []; - try { - rawProviderInfo = await this._telemetrySettings.get(); - } catch (ex) { - logConsole.error("Could not get settings:", ex); - }
// Send the provider info to the child handler. this._contentHandler.init(rawProviderInfo); diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.cpp b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp index 1bbc7a6524864..c3ab7c6cefc50 100644 --- a/netwerk/url-classifier/UrlClassifierFeatureBase.cpp +++ b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp @@ -78,7 +78,7 @@ void UrlClassifierFeatureBase::InitializePreferences() {
nsCOMPtr<nsIUrlClassifierExceptionListService> exceptionListService = do_GetService("@mozilla.org/url-classifier/exception-list-service;1"); - if (NS_WARN_IF(!exceptionListService)) { + if (!exceptionListService) { return; }
diff --git a/netwerk/url-classifier/components.conf b/netwerk/url-classifier/components.conf index 03a02f0ebeab7..b2e6672473177 100644 --- a/netwerk/url-classifier/components.conf +++ b/netwerk/url-classifier/components.conf @@ -13,10 +13,4 @@ Classes = [ 'constructor': 'mozilla::net::ChannelClassifierService::GetSingleton', 'headers': ['mozilla/net/ChannelClassifierService.h'], }, - { - 'cid': '{b9f4fd03-9d87-4bfd-9958-85a821750ddc}', - 'contract_ids': ['@mozilla.org/url-classifier/exception-list-service;1'], - 'jsm': 'resource://gre/modules/UrlClassifierExceptionListService.jsm', - 'constructor': 'UrlClassifierExceptionListService', - }, ] diff --git a/security/manager/ssl/RemoteSecuritySettings.jsm b/security/manager/ssl/RemoteSecuritySettings.jsm index 630cfc18f498d..d9a4f27a263fb 100644 --- a/security/manager/ssl/RemoteSecuritySettings.jsm +++ b/security/manager/ssl/RemoteSecuritySettings.jsm @@ -274,6 +274,16 @@ var RemoteSecuritySettings = {
class IntermediatePreloads { constructor() { + this.maybeInit(); + } + + maybeInit() { + if ( + this.client || + !Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true) + ) { + return; + } this.client = RemoteSettings( Services.prefs.getCharPref(INTERMEDIATES_COLLECTION_PREF), { @@ -303,6 +313,7 @@ class IntermediatePreloads { ); return; } + this.maybeInit();
// Download attachments that are awaiting download, up to a max. const maxDownloadsPerRun = Services.prefs.getIntPref( @@ -544,6 +555,16 @@ function compareFilters(filterA, filterB) {
class CRLiteFilters { constructor() { + this.maybeInit(); + } + + maybeInit() { + if ( + this.client || + !Services.prefs.getBoolPref(CRLITE_FILTERS_ENABLED_PREF, true) + ) { + return; + } this.client = RemoteSettings( Services.prefs.getCharPref(CRLITE_FILTERS_COLLECTION_PREF), { @@ -571,6 +592,8 @@ class CRLiteFilters { return; }
+ this.maybeInit(); + let hasPriorFilter = await hasPriorData( Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_FULL ); diff --git a/services/settings/IDBHelpers.jsm b/services/settings/IDBHelpers.jsm index 5dc59c3687ef0..010a5ea82987d 100644 --- a/services/settings/IDBHelpers.jsm +++ b/services/settings/IDBHelpers.jsm @@ -188,6 +188,10 @@ async function openIDB(allowUpgrades = true) { }); } if (event.oldVersion < 3) { + // Clear existing stores for a fresh start + transaction.objectStore("records").clear(); + transaction.objectStore("timestamps").clear(); + transaction.objectStore("collections").clear(); // Attachment store db.createObjectStore("attachments", { keyPath: ["cid", "attachmentId"], diff --git a/services/settings/dumps/blocklists/moz.build b/services/settings/dumps/blocklists/moz.build index 825fcd1f10f58..4ca18acd4ff65 100644 --- a/services/settings/dumps/blocklists/moz.build +++ b/services/settings/dumps/blocklists/moz.build @@ -8,15 +8,11 @@ with Files("**"): BUG_COMPONENT = ("Toolkit", "Blocklist Implementation")
# The addons blocklist is also in mobile/android/installer/package-manifest.in -if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": - # Remove this once bug 1639050 is resolved. - FINAL_TARGET_FILES.defaults.settings.blocklists += ["addons.json"] -else: - FINAL_TARGET_FILES.defaults.settings.blocklists += [ - "addons-bloomfilters.json", - "gfx.json", - "plugins.json", - ] +FINAL_TARGET_FILES.defaults.settings.blocklists += [ + "addons-bloomfilters.json", + "gfx.json", + "plugins.json", +]
FINAL_TARGET_FILES.defaults.settings.blocklists["addons-bloomfilters"] += [ "addons-bloomfilters/addons-mlbf.bin", diff --git a/services/settings/dumps/main/moz.build b/services/settings/dumps/main/moz.build index 373822df3af2e..6deac0b6f5bc9 100644 --- a/services/settings/dumps/main/moz.build +++ b/services/settings/dumps/main/moz.build @@ -3,18 +3,11 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
FINAL_TARGET_FILES.defaults.settings.main += [ - "anti-tracking-url-decoration.json", "example.json", "hijack-blocklists.json", "language-dictionaries.json", - "password-recipes.json", "password-rules.json", - "search-config.json", "search-default-override-allowlist.json", - "search-telemetry-v2.json", - "sites-classification.json", - "top-sites.json", - "url-classifier-skip-urls.json", "websites-with-shared-credential-backends.json", ]
diff --git a/services/settings/dumps/security-state/moz.build b/services/settings/dumps/security-state/moz.build index 9133cd4e3ed69..0d250ecddbe89 100644 --- a/services/settings/dumps/security-state/moz.build +++ b/services/settings/dumps/security-state/moz.build @@ -3,7 +3,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
FINAL_TARGET_FILES.defaults.settings["security-state"] += [ - "intermediates.json", "onecrl.json", ]
diff --git a/toolkit/components/antitracking/antitracking.manifest b/toolkit/components/antitracking/antitracking.manifest index 5eb37f9a3f99d..872e6af07575a 100644 --- a/toolkit/components/antitracking/antitracking.manifest +++ b/toolkit/components/antitracking/antitracking.manifest @@ -1 +1 @@ -category profile-after-change URLDecorationAnnotationsService @mozilla.org/tracking-url-decoration-service;1 process=main +# category profile-after-change URLDecorationAnnotationsService @mozilla.org/tracking-url-decoration-service;1 process=main diff --git a/toolkit/components/antitracking/components.conf b/toolkit/components/antitracking/components.conf index b2579fd1512dc..1a1c90ebb309a 100644 --- a/toolkit/components/antitracking/components.conf +++ b/toolkit/components/antitracking/components.conf @@ -11,13 +11,6 @@ Classes = [ 'jsm': 'resource://gre/modules/TrackingDBService.jsm', 'constructor': 'TrackingDBService', }, - { - 'cid': '{5874af6d-5719-4e1b-b155-ef4eae7fcb32}', - 'contract_ids': ['@mozilla.org/tracking-url-decoration-service;1'], - 'jsm': 'resource://gre/modules/URLDecorationAnnotationsService.jsm', - 'constructor': 'URLDecorationAnnotationsService', - 'processes': ProcessSelector.MAIN_PROCESS_ONLY, - }, { 'cid': '{90d1fd17-2018-4e16-b73c-a04a26fa6dd4}', 'contract_ids': ['@mozilla.org/purge-tracker-service;1'], diff --git a/toolkit/components/search/SearchService.jsm b/toolkit/components/search/SearchService.jsm index 98fb8be74d866..58c3eaec2c3e1 100644 --- a/toolkit/components/search/SearchService.jsm +++ b/toolkit/components/search/SearchService.jsm @@ -256,8 +256,6 @@ SearchService.prototype = { // See if we have a settings file so we don't have to parse a bunch of XML. let settings = await this._settings.get();
- this._setupRemoteSettings().catch(Cu.reportError); - await this._loadEngines(settings);
// If we've got this far, but the application is now shutting down,
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 5f704c50b8cc06b44be716860c0cee02e398cad4 Author: Georg Koppen gk@torproject.org AuthorDate: Wed May 29 12:29:19 2019 +0000
Bug 30541: Disable WebGL readPixel() for web content
Related Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1428034 --- dom/canvas/ClientWebGLContext.cpp | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp index 7f7dc1e85c307..bba4fccc1fab5 100644 --- a/dom/canvas/ClientWebGLContext.cpp +++ b/dom/canvas/ClientWebGLContext.cpp @@ -4654,6 +4654,14 @@ bool ClientWebGLContext::ReadPixels_SharedPrecheck( return false; }
+ // Security check passed, but don't let content readPixel calls through for + // now, if Resist Fingerprinting Mode is enabled. + if (nsContentUtils::ResistFingerprinting(aCallerType)) { + JsWarning("readPixels: Not allowed in Resist Fingerprinting Mode"); + out_error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return false; + } + return true; }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 3150d641887f8e760d8a74ab5b052ee603fd8bca Author: Alex Catarineu acat@torproject.org AuthorDate: Wed Apr 10 17:52:51 2019 +0200
Bug 28369: Stop shipping pingsender executable --- browser/app/macbuild/Contents/MacOS-files.in | 1 - browser/installer/package-manifest.in | 4 ---- browser/installer/windows/nsis/shared.nsh | 1 - python/mozbuild/mozbuild/artifacts.py | 2 -- toolkit/components/telemetry/app/TelemetrySend.jsm | 19 +------------------ toolkit/components/telemetry/moz.build | 4 ---- 6 files changed, 1 insertion(+), 30 deletions(-)
diff --git a/browser/app/macbuild/Contents/MacOS-files.in b/browser/app/macbuild/Contents/MacOS-files.in index 6f0b4481473bb..6e8a1689ea19f 100644 --- a/browser/app/macbuild/Contents/MacOS-files.in +++ b/browser/app/macbuild/Contents/MacOS-files.in @@ -17,7 +17,6 @@ #if defined(MOZ_CRASHREPORTER) /minidump-analyzer #endif -/pingsender /pk12util /ssltunnel /xpcshell diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 0c9051642686b..26f093c500645 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -428,10 +428,6 @@ bin/libfreebl_64int_3.so @BINPATH@/minidump-analyzer@BIN_SUFFIX@ #endif
-; [ Ping Sender ] -; -@BINPATH@/pingsender@BIN_SUFFIX@ - ; Shutdown Terminator @RESPATH@/components/terminator.manifest
diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh index beeb67211e47a..7439ffd33e3e9 100755 --- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -1478,7 +1478,6 @@ ${RemoveDefaultBrowserAgentShortcut} Push "crashreporter.exe" Push "default-browser-agent.exe" Push "minidump-analyzer.exe" - Push "pingsender.exe" Push "updater.exe" Push "mozwer.dll" Push "${FileMainEXE}" diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py index ee05eb2b2e881..f99f08b21d1cb 100644 --- a/python/mozbuild/mozbuild/artifacts.py +++ b/python/mozbuild/mozbuild/artifacts.py @@ -506,7 +506,6 @@ class LinuxArtifactJob(ArtifactJob): "{product}/{product}", "{product}/{product}-bin", "{product}/minidump-analyzer", - "{product}/pingsender", "{product}/plugin-container", "{product}/updater", "{product}/**/*.so", @@ -561,7 +560,6 @@ class MacArtifactJob(ArtifactJob): "{product}-bin", "*.dylib", "minidump-analyzer", - "pingsender", "plugin-container.app/Contents/MacOS/plugin-container", "updater.app/Contents/MacOS/org.mozilla.updater", # 'xpcshell', diff --git a/toolkit/components/telemetry/app/TelemetrySend.jsm b/toolkit/components/telemetry/app/TelemetrySend.jsm index 0da39d85ad339..c87a281019e4d 100644 --- a/toolkit/components/telemetry/app/TelemetrySend.jsm +++ b/toolkit/components/telemetry/app/TelemetrySend.jsm @@ -1595,23 +1595,6 @@ var TelemetrySendImpl = { },
runPingSender(pings, observer) { - if (AppConstants.platform === "android") { - throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); - } - - const exeName = - AppConstants.platform === "win" ? "pingsender.exe" : "pingsender"; - - let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile); - exe.append(exeName); - - let params = pings.flatMap(ping => [ping.url, ping.path]); - let process = Cc["@mozilla.org/process/util;1"].createInstance( - Ci.nsIProcess - ); - process.init(exe); - process.startHidden = true; - process.noShell = true; - process.runAsync(params, params.length, observer); + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); }, }; diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index 3eee4e938c4ef..cedf9b313d9c3 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -8,10 +8,6 @@ include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"
-DIRS = [ - "pingsender", -] - DEFINES["MOZ_APP_VERSION"] = '"%s"' % CONFIG["MOZ_APP_VERSION"]
LOCAL_INCLUDES += [
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 568fe5cb0bb7d25b24b5525ba892c3c5c13330e1 Author: Alex Catarineu acat@torproject.org AuthorDate: Thu Aug 13 11:05:03 2020 +0200
Bug 40073: Disable remote Public Suffix List fetching
In https://bugzilla.mozilla.org/show_bug.cgi?id=1563246 Firefox implemented fetching the Public Suffix List via RemoteSettings and replacing the default one at runtime, which we do not want. --- browser/components/BrowserGlue.jsm | 5 ----- 1 file changed, 5 deletions(-)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 5ebd1cd6515a7..a765b6f9ee2d6 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -71,7 +71,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { PluralForm: "resource://gre/modules/PluralForm.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm", - PublicSuffixList: "resource://gre/modules/netwerk-dns/PublicSuffixList.jsm", RemoteSettings: "resource://services-settings/remote-settings.js", RemoteSecuritySettings: "resource://gre/modules/psm/RemoteSecuritySettings.jsm", @@ -2668,10 +2667,6 @@ BrowserGlue.prototype = { this._addBreachesSyncHandler(); },
- () => { - PublicSuffixList.init(); - }, - () => { RemoteSecuritySettings.init(); },
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit ad8c976086edf1de4abae1de842ff222d89a40cd Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Tue Sep 10 18:20:43 2013 -0700
Firefox preference overrides.
This hack directly includes our preference changes in omni.ja.
Bug 18292: Staged updates fail on Windows
Temporarily disable staged updates on Windows.
Bug 18297: Use separate Noto JP,KR,SC,TC fonts
Bug 23404: Add Noto Sans Buginese to the macOS whitelist
Bug 23745: Set dom.indexedDB.enabled = true
Bug 13575: Disable randomised Firefox HTTP cache decay user tests. (Fernando Fernandez Mancera ffmancera@riseup.net)
Bug 17252: Enable session identifiers with FPI
Session tickets and session identifiers were isolated by OriginAttributes, so we can re-enable them by allowing the default value (true) of "security.ssl.disable_session_identifiers".
The pref "security.enable_tls_session_tickets" is obsolete (removed in https://bugzilla.mozilla.org/917049)
Bug 14952: Enable http/2 and AltSvc
In Firefox, SPDY/HTTP2 now uses Origin Attributes for isolation of connections, push streams, origin frames, etc. That means we get first-party isolation provided "privacy.firstparty.isolate" is true. So in this patch, we stop overriding "network.http.spdy.enabled" and "network.http.spdy.enabled.http2".
Alternate Services also use Origin Attributes for isolation. So we stop overriding "network.http.altsvc.enabled" and "network.http.altsvc.oe" as well.
(All 4 of the abovementioned "network.http.*" prefs adopt Firefox 60ESR's default value of true.)
However, we want to disable HTTP/2 push for now, so we set "network.http.spdy.allow-push" to false.
"network.http.spdy.enabled.http2draft" was removed in Bug 1132357. "network.http.sped.enabled.v2" was removed in Bug 912550. "network.http.sped.enabled.v3" was removed in Bug 1097944. "network.http.sped.enabled.v3-1" was removed in Bug 1248197.
Bug 26114: addons.mozilla.org is not special * Don't expose navigator.mozAddonManager on any site * Don't block NoScript from modifying addons.mozilla.org or other sites
Enable ReaderView mode again (#27281).
Bug 29916: Make sure enterprise policies are disabled
Bug 2874: Block Components.interfaces from content
Bug 26146: Spoof HTTP User-Agent header for desktop platforms
In Tor Browser 8.0, the OS was revealed in both the HTTP User-Agent header and to JavaScript code via navigator.userAgent. To avoid leaking the OS inside each HTTP request (which many web servers log), always use the Windows 7 OS value in the desktop User-Agent header. We continue to allow access to the actual OS via JavaScript, since doing so improves compatibility with web applications such as GitHub and Google Docs.
Bug 12885: Windows Jump Lists fail for Tor Browser
Jumplist entries are stored in a binary file in: %APPDATA%\Microsoft\Windows\Recent\CustomDestinations\ and has a name in the form [a-f0-9]+.customDestinations-ms
The hex at the front is unique per app, and is ultimately derived from something called the 'App User Model ID' (AUMID) via some unknown hashing method. The AUMID is provided as a key when programmatically creating, updating, and deleting a jumplist. The default behaviour in firefox is for the installer to define an AUMID for an app, and save it in the registry so that the jumplist data can be removed by the uninstaller.
However, the Tor Browser does not set this (or any other) regkey during installation, so this codepath fails and the app's AUMID is left undefined. As a result the app's AUMID ends up being defined by windows, but unknowable by Tor Browser. This unknown AUMID is used to create and modify the jumplist, but the delete API requires that we provide the app's AUMID explicitly. Since we don't know what the AUMID is (since the expected regkey where it is normally stored does not exist) jumplist deletion will fail and we will leave behind a mostly empty customDestinations-ms file. The name of the file is derived from the binary path, so an enterprising person could reverse engineer how that hex name is calculated, and generate the name for Tor Browser's default Desktop installation path to determine whether a person had used Tor Browser in the past.
The 'taskbar.grouping.useprofile' option that is enabled by this patch works around this AUMID problem by having firefox.exe create it's own AUMID based on the profile path (rather than looking for a regkey). This way, if a user goes in and enables and disables jumplist entries, the backing store is properly deleted.
Unfortunately, all windows users currently have this file lurking in the above mentioned directory and this patch will not remove it since it was created with an unknown AUMID. However, another patch could be written which goes to that directory and deletes any item containing the 'Tor Browser' string. See bug 28996.
Bug 30845: Make sure default themes and other internal extensions are enabled
Bug 28896: Enable extensions in private browsing by default
Bug 31065: Explicitly allow proxying localhost
Bug 31598: Enable letterboxing
Disable Presentation API everywhere
Bug 21549 - Use Firefox's WASM default pref. It is disabled at safer security levels.
Bug 32321: Disable Mozilla's MitM pings
Bug 19890: Disable installation of system addons
By setting the URL to "" we make sure that already installed system addons get deleted as well.
Bug 22548: Firefox downgrades VP9 videos to VP8.
On systems where H.264 is not available or no HWA, VP9 is preferred. But in Tor Browser 7.0 all youtube videos are degraded to VP8.
This behaviour can be turned off by setting media.benchmark.vp9.threshold to 0. All clients will get better experience and lower traffic, beause TBB doesn't use "Use hardware acceleration when available".
Bug 25741 - TBA: Add mobile-override of 000-tor-browser prefs
Bug 16441: Suppress "Reset Tor Browser" prompt.
Bug 29120: Use the in-memory media cache and increase its maximum size.
Bug 33697: use old search config based on list.json
Bug 33855: Ensure that site-specific browser mode is disabled.
Bug 30682: Disable Intermediate CA Preloading.
Bug 40061: Omit the Windows default browser agent from the build
Bug 40322: Consider disabling network.connectivity-service.enabled
Bug 40408: Disallow SVG Context Paint in all web content
Bug 40308: Disable network partitioning until we evaluate dFPI
Bug 40322: Consider disabling network.connectivity-service.enabled
Bug 40383: Disable dom.enable_event_timing
Bug 40423: Disable http/3
Bug 40177: Update prefs for Fx91esr
Bug 40700: Disable addons and features recommendations
Bug 40682: Disable network.proxy.allow_bypass
Bug 40736: Disable third-party cookies in PBM
Bug 19850: Enabled HTTPS-Only by default
Bug 40912: Hide the screenshot menu --- .eslintignore | 3 + browser/app/profile/001-base-profile.js | 584 ++++++++++++++++++++++++++++++++ browser/app/profile/firefox.js | 6 +- browser/installer/package-manifest.in | 1 + browser/moz.build | 1 + browser/themes/shared/menupanel.inc.css | 1 + mobile/android/app/geckoview-prefs.js | 2 + mobile/android/app/mobile.js | 4 + mobile/android/app/moz.build | 1 + taskcluster/ci/source-test/mozlint.yml | 1 + 10 files changed, 601 insertions(+), 3 deletions(-)
diff --git a/.eslintignore b/.eslintignore index c551245983a6e..f518ff2c6f7bd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -136,6 +136,9 @@ js/src/Y.js # Fuzzing code for testing only, targeting the JS shell js/src/fuzz-tests/
+# uses `#include` +mobile/android/app/000-tor-browser-android.js + # Uses `#filter substitution` mobile/android/app/mobile.js mobile/android/app/geckoview-prefs.js diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js new file mode 100644 index 0000000000000..b6383cabaa095 --- /dev/null +++ b/browser/app/profile/001-base-profile.js @@ -0,0 +1,584 @@ +// Preferences to harden Firefox's security and privacy +// Do not edit this file. + +// Disable initial homepage notifications +pref("browser.search.update", false); +pref("browser.rights.3.shown", true); +pref("startup.homepage_welcome_url", ""); +pref("startup.homepage_welcome_url.additional", ""); + +// Disable Firefox Welcome Dialog +pref("browser.aboutwelcome.enabled", false); + +// Set a generic, default URL that will be opened in a tab after an update. +// Typically, this will not be used; instead, the <update> element within +// each update manifest should contain attributes similar to: +// actions="showURL" +// openURL="https://blog.torproject.org/tor-browser-55a2-released" +pref("startup.homepage_override_url", "https://blog.torproject.org/category/applications"); + +// Try to nag a bit more about updates: Pop up a restart dialog an hour after the initial dialog +pref("app.update.promptWaitTime", 3600); + +#ifndef XP_MACOSX +// Disable staged updates on platforms other than macOS. +// Staged updates do not work on Windows due to #18292. +// Also, on Windows and Linux any changes that are made to the browser profile +// or Tor data after an update is staged will be lost. +pref("app.update.staging.enabled", false); +#endif + +// Disable "Slow startup" warnings and associated disk history +// (bug #13346) +pref("browser.slowStartup.notificationDisabled", true); +pref("browser.slowStartup.maxSamples", 0); +pref("browser.slowStartup.samples", 0); + +// Disable the "Refresh" prompt that is displayed for stale profiles. +pref("browser.disableResetPrompt", true); + +// Disk activity: Disable Browsing History Storage +pref("browser.privatebrowsing.autostart", true); +pref("browser.cache.disk.enable", false); +pref("permissions.memory_only", true); +pref("network.cookie.lifetimePolicy", 2); +pref("security.nocertdb", true); + +// Enabled LSNG +pref("dom.storage.next_gen", true); + +// Disk activity: TBB Directory Isolation +pref("browser.download.useDownloadDir", false); +pref("browser.download.manager.addToRecentDocs", false); + +// Misc privacy: Disk +pref("signon.rememberSignons", false); +pref("browser.formfill.enable", false); +pref("signon.autofillForms", false); +pref("browser.sessionstore.privacy_level", 2); +// Use the in-memory media cache and increase its maximum size (#29120) +pref("browser.privatebrowsing.forceMediaMemoryCache", true); +pref("media.memory_cache_max_size", 16384); + +// Enable HTTPS-Only mode +pref("dom.security.https_only_mode", true); +pref("dom.security.https_only_mode.upgrade_onion", false); + +// Misc privacy: Remote +pref("browser.send_pings", false); +pref("geo.enabled", false); +pref("geo.provider.network.url", ""); +pref("browser.search.suggest.enabled", false); +pref("browser.safebrowsing.malware.enabled", false); +pref("browser.safebrowsing.phishing.enabled", false); +pref("browser.safebrowsing.downloads.enabled", false); +pref("browser.safebrowsing.downloads.remote.enabled", false); +pref("browser.safebrowsing.blockedURIs.enabled", false); +pref("browser.safebrowsing.downloads.remote.url", ""); +pref("browser.safebrowsing.provider.google.updateURL", ""); +pref("browser.safebrowsing.provider.google.gethashURL", ""); +pref("browser.safebrowsing.provider.google4.updateURL", ""); +pref("browser.safebrowsing.provider.google4.gethashURL", ""); +pref("browser.safebrowsing.provider.mozilla.updateURL", ""); +pref("browser.safebrowsing.provider.mozilla.gethashURL", ""); +pref("extensions.ui.lastCategory", "addons://list/extension"); +pref("datareporting.healthreport.uploadEnabled", false); +pref("datareporting.policy.dataSubmissionEnabled", false); +// Make sure Unified Telemetry is really disabled, see: #18738. +pref("toolkit.telemetry.unified", false); +pref("toolkit.telemetry.enabled", false); +pref("toolkit.telemetry.updatePing.enabled", false); // Make sure updater telemetry is disabled; see #25909. +#ifdef XP_WIN +// Defense-in-depth: ensure that the Windows default browser agent will +// not ping Mozilla if it is somehow present (we omit it at build time). +pref("default-browser-agent.enabled", false); +#endif +pref("identity.fxaccounts.enabled", false); // Disable sync by default +pref("services.sync.engine.prefs", false); // Never sync prefs, addons, or tabs with other browsers +pref("services.sync.engine.addons", false); +pref("services.sync.engine.tabs", false); +pref("extensions.getAddons.cache.enabled", false); // https://blog.mozilla.org/addons/how-to-opt-out-of-add-on-metadata-updates/ +pref("browser.newtabpage.enabled", false); +pref("browser.search.region", "US"); // The next two prefs disable GeoIP search lookups (#16254) +pref("browser.search.geoip.url", ""); +pref("browser.fixup.alternate.enabled", false); // Bug #16783: Prevent .onion fixups +// Make sure there is no Tracking Protection active in Tor Browser, see: #17898. +pref("privacy.trackingprotection.enabled", false); +pref("privacy.trackingprotection.pbmode.enabled", false); +pref("privacy.trackingprotection.annotate_channels", false); +pref("privacy.trackingprotection.cryptomining.enabled", false); +pref("privacy.trackingprotection.fingerprinting.enabled", false); +pref("privacy.trackingprotection.socialtracking.enabled", false); +pref("privacy.socialtracking.block_cookies.enabled", false); +pref("privacy.annotate_channels.strict_list.enabled", false); + +// Disable the Pocket extension (Bug #18886 and #31602) +pref("extensions.pocket.enabled", false); + +// Disable the screenshot menu when right-clicking (Bug #40912 and #40007) +pref("extensions.screenshots.disabled", true); +pref("extensions.webcompat-reporter.enabled", false); + +// Disable use of WiFi location information +pref("browser.region.network.scan", false); +pref("browser.region.network.url", ""); +// Bug 40083: Make sure Region.jsm fetching is disabled +pref("browser.region.update.enabled", false); + +// Don't load Mozilla domains in a separate tab process +pref("browser.tabs.remote.separatedMozillaDomains", ""); + +// Avoid DNS lookups on search terms +pref("browser.urlbar.dnsResolveSingleWordsAfterSearch", 0); + +// Disable about:newtab and "first run" experiments +pref("messaging-system.rsexperimentloader.enabled", false); +pref("trailhead.firstrun.branches", ""); + +// [SETTING] General>Browsing>Recommend extensions as you browse (Bug #40700) +pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false); // disable CFR [FF67+] + +// [SETTING] General>Browsing>Recommend features as you browse (Bug #40700) +pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false); // disable CFR [FF67+] + +// Clear the list of trusted recursive resolver services +pref("network.trr.resolvers", ""); + +// Disable the /etc/hosts parser +pref("network.trr.exclude-etc-hosts", false); + +// Disable crlite +pref("security.pki.crlite_mode", 0); + +// Disable website password breach alerts +pref("signon.management.page.breach-alerts.enabled", false); +pref("extensions.fxmonitor.enabled", false); + +// Remove mobile app tracking URLs +pref("signon.management.page.mobileAndroidURL", ""); +pref("signon.management.page.mobileAppleURL", ""); + +// Disable remote "password recipes" +pref("signon.recipes.remoteRecipesEnabled", false); + +// Disable ServiceWorkers and push notifications by default +pref("dom.serviceWorkers.enabled", false); +pref("dom.push.enabled", false); + +// Fingerprinting +pref("webgl.disable-fail-if-major-performance-caveat", true); +pref("webgl.enable-webgl2", false); +pref("gfx.downloadable_fonts.fallback_delay", -1); +pref("browser.startup.homepage_override.buildID", "20100101"); +pref("browser.link.open_newwindow.restriction", 0); // Bug 9881: Open popups in new tabs (to avoid fullscreen popups) +// Set video VP9 to 0 for everyone (bug 22548) +pref("media.benchmark.vp9.threshold", 0); +pref("dom.enable_resource_timing", false); // Bug 13024: To hell with this API +pref("privacy.resistFingerprinting", true); +pref("privacy.resistFingerprinting.block_mozAddonManager", true); // Bug 26114 +pref("dom.webaudio.enabled", false); // Bug 13017: Disable Web Audio API +pref("dom.w3c_touch_events.enabled", 0); // Bug 10286: Always disable Touch API +pref("dom.vr.enabled", false); // Bug 21607: Disable WebVR for now +pref("security.webauth.webauthn", false); // Bug 26614: Disable Web Authentication API for now +// Disable SAB, no matter if the sites are cross-origin isolated. +pref("dom.postMessage.sharedArrayBuffer.withCOOP_COEP", false); +// Disable intermediate preloading (Bug 30682) +pref("security.remote_settings.intermediates.enabled", false); +// Bug 2874: Block Components.interfaces from content +pref("dom.use_components_shim", false); +// Enable letterboxing +pref("privacy.resistFingerprinting.letterboxing", true); +// Disable network information API everywhere. It gets spoofed in bug 1372072 +// but, alas, the behavior is inconsistent across platforms, see: +// https://trac.torproject.org/projects/tor/ticket/27268#comment:19. We should +// not leak that difference if possible. +pref("dom.netinfo.enabled", false); +pref("network.http.referer.defaultPolicy", 2); // Bug 32948: Make referer behavior consistent regardless of private browing mode status +pref("media.videocontrols.picture-in-picture.enabled", false); // Bug 40148: disable until audited in #40147 +pref("network.http.referer.hideOnionSource", true); +// Bug 40463: Disable Windows SSO +pref("network.http.windows-sso.enabled", false); +// Bug 40383: Disable new PerformanceEventTiming +pref("dom.enable_event_timing", false); +// Disable API for measuring text width and height. +pref("dom.textMetrics.actualBoundingBox.enabled", false); +pref("dom.textMetrics.baselines.enabled", false); +pref("dom.textMetrics.emHeight.enabled", false); +pref("dom.textMetrics.fontBoundingBox.enabled", false); +pref("pdfjs.enableScripting", false); +pref("javascript.options.large_arraybuffers", false); + +// Third party stuff +pref("privacy.firstparty.isolate", true); // Always enforce first party isolation +pref("privacy.partition.network_state", false); // Disable for now until audit +pref("network.cookie.cookieBehavior", 1); +pref("network.cookie.cookieBehavior.pbmode", 1); +pref("network.http.spdy.allow-push", false); // Disabled for now. See https://bugs.torproject.org/27127 +pref("network.predictor.enabled", false); // Temporarily disabled. See https://bugs.torproject.org/16633 +// Bug 40177: Make sure tracker cookie purging is disabled +pref("privacy.purge_trackers.enabled", false); + +pref("network.dns.disablePrefetch", true); +pref("network.protocol-handler.external-default", false); +pref("network.protocol-handler.external.mailto", false); +pref("network.protocol-handler.external.news", false); +pref("network.protocol-handler.external.nntp", false); +pref("network.protocol-handler.external.snews", false); +pref("network.protocol-handler.warn-external.mailto", true); +pref("network.protocol-handler.warn-external.news", true); +pref("network.protocol-handler.warn-external.nntp", true); +pref("network.protocol-handler.warn-external.snews", true); +pref("network.proxy.allow_bypass", false); // #40682 +// Make sure we don't have any GIO supported protocols (defense in depth +// measure) +pref("network.gio.supported-protocols", ""); +pref("media.peerconnection.enabled", false); // Disable WebRTC interfaces +// Disables media devices but only if `media.peerconnection.enabled` is set to +// `false` as well. (see bug 16328 for this defense-in-depth measure) +pref("media.navigator.enabled", false); +// GMPs (Gecko Media Plugins, https://wiki.mozilla.org/GeckoMediaPlugins) +// We make sure they don't show up on the Add-on panel and confuse users. +// And the external update/donwload server must not get pinged. We apply a +// clever solution for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=769716. +pref("media.gmp-provider.enabled", false); +pref("media.gmp-manager.url.override", "data:text/plain,"); +// Since ESR52 it is not enough anymore to block pinging the GMP update/download +// server. There is a local fallback that must be blocked now as well. See: +// https://bugzilla.mozilla.org/show_bug.cgi?id=1267495. +pref("media.gmp-manager.updateEnabled", false); +// Mozilla is relying on preferences to make sure no DRM blob is downloaded and +// run. Even though those prefs should be set correctly by specifying +// --disable-eme (which we do), we disable all of them here as well for defense +// in depth (see bug 16285 for more details). +pref("browser.eme.ui.enabled", false); +pref("media.gmp-widevinecdm.visible", false); +pref("media.gmp-widevinecdm.enabled", false); +pref("media.eme.enabled", false); +pref("media.mediadrm-widevinecdm.visible", false); +// WebIDE can bypass proxy settings for remote debugging. It also downloads +// some additional addons that we have not reviewed. Turn all that off. +pref("devtools.webide.autoinstallADBExtension", false); +pref("devtools.webide.enabled", false); +// The in-browser debugger for debugging chrome code is not coping with our +// restrictive DNS look-up policy. We use "127.0.0.1" instead of "localhost" as +// a workaround. See bug 16523 for more details. +pref("devtools.debugger.chrome-debugging-host", "127.0.0.1"); +// Disable using UNC paths (bug 26424 and Mozilla's bug 1413868) +pref("network.file.disable_unc_paths", true); +// Enhance our treatment of file:// to avoid proxy bypasses (see Mozilla's bug +// 1412081) +pref("network.file.path_blacklist", "/net"); + +// Security slider +pref("svg.disabled", false); +pref("mathml.disabled", false); + +// Bug 40408 +pref("svg.context-properties.content.allowed-domains", ""); + +// Network and performance +pref("security.ssl.enable_false_start", true); +pref("network.http.connection-retry-timeout", 0); +pref("network.manage-offline-status", false); +// No need to leak things to Mozilla, see bug 21790 and tor-browser#40322 +pref("network.captive-portal-service.enabled", false); +pref("network.connectivity-service.enabled", false); +// As a "defense in depth" measure, configure an empty push server URL (the +// DOM Push features are disabled by default via other prefs). +pref("dom.push.serverURL", ""); +// Bug 40423: Disable http/3 +pref("network.http.http3.enabled", false); + +// Extension support +pref("extensions.autoDisableScopes", 0); +pref("extensions.bootstrappedAddons", "{}"); +pref("extensions.checkCompatibility.4.*", false); +pref("extensions.databaseSchema", 3); +pref("extensions.enabledScopes", 5); // AddonManager.SCOPE_PROFILE=1 | AddonManager.SCOPE_APPLICATION=4 +pref("extensions.pendingOperations", false); +// We don't know what extensions Mozilla is advertising to our users and we +// don't want to have some random Google Analytics script running either on the +// about:addons page, see bug 22073, 22900 and 31601. +pref("extensions.getAddons.showPane", false); +pref("extensions.htmlaboutaddons.recommendations.enabled", false); +// Bug 26114: Allow NoScript to access addons.mozilla.org etc. +pref("extensions.webextensions.restrictedDomains", ""); +// Don't give Mozilla-recommended third-party extensions special privileges. +pref("extensions.postDownloadThirdPartyPrompt", false); + +// Toolbar layout +pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-list":[],"PersonalToolbar":["personal-bookmarks"],"nav-bar":["back-button","forward-button","stop-reload-button","urlbar-container","torbutton-button","security-level-button","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","new- [...] + +// Enforce certificate pinning, see: https://bugs.torproject.org/16206 +pref("security.cert_pinning.enforcement_level", 2); + +// Don't load OS client certs. +pref("security.osclientcerts.autoload", false); + +// Don't allow MitM via Microsoft Family Safety, see bug 21686 +pref("security.family_safety.mode", 0); + +// Don't allow MitM via enterprise roots, see bug 30681 +pref("security.enterprise_roots.enabled", false); + +// Don't ping Mozilla for MitM detection, see bug 32321 +pref("security.certerrors.mitm.priming.enabled", false); + +// Don't automatically enable enterprise roots, see bug 40166 +pref("security.certerrors.mitm.auto_enable_enterprise_roots", false); + +// Disable the language pack signing check for now on macOS, see #31942 +#ifdef XP_MACOSX +pref("extensions.langpacks.signatures.required", false); +#endif + +// Disable special URL bar behaviors +pref("browser.urlbar.suggest.topsites", false); +pref("browser.urlbar.update1.interventions", false); +pref("browser.urlbar.update1.searchTips", false); + +// Skip checking omni.ja and other files for corruption since the result +// is only reported via telemetry (which is disabled). +pref("corroborator.enabled", false); + +// prefs to disable jump-list entries in the taskbar on Windows (see bug #12885) +#ifdef XP_WIN +// this pref changes the app's set AUMID to be dependent on the profile path, rather than +// attempting to read it from the registry; this is necessary so that the file generated +// by the jumplist system can be properly deleted if it is disabled +pref("taskbar.grouping.useprofile", true); +pref("browser.taskbar.lists.enabled", false); +pref("browser.taskbar.lists.frequent.enabled", false); +pref("browser.taskbar.lists.tasks.enabled", false); +pref("browser.taskbar.lists.recent.enabled", false); +#endif + +// Disable Presentation API +pref("dom.presentation.controller.enabled", false); +pref("dom.presentation.enabled", false); +pref("dom.presentation.discoverable", false); +pref("dom.presentation.discoverable.encrypted", false); +pref("dom.presentation.discovery.enabled", false); +pref("dom.presentation.receiver.enabled", false); + +pref("dom.audiochannel.audioCompeting", false); +pref("dom.audiochannel.mediaControl", false); + +// If we are bundling fonts, whitelist those bundled fonts, and restrict system fonts to a selection. + +#ifdef MOZ_BUNDLED_FONTS + +// Bug 40342: Always use bundled fonts +pref("gfx.bundled-fonts.activate", 1); + +#ifdef XP_MACOSX +pref("font.system.whitelist", "AppleGothic, Apple Color Emoji, Arial, Courier, Geneva, Georgia, Heiti TC, Helvetica, Helvetica Neue, .Helvetica Neue DeskInterface, Hiragino Kaku Gothic ProN, Lucida Grande, Menlo, Monaco, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sa [...] +pref("font.name-list.cursive.x-unicode", "Apple Chancery, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongolian, Noto Sans Myanmar, Noto Sans Oriya, Noto Sans Sinhala, Noto Sans Tamil, Noto Sans Telugu, Noto Sans Thaana, Noto Sans Tibetan, Noto Sans Yi"); +pref("font.name-list.fantasy.x-unicode", "Papyrus, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongolian, Noto Sans Myanmar, Noto Sans Oriya, Noto Sans Sinhala, Noto Sans Tamil, Noto Sans Telugu, Noto Sans Thaana, Noto Sans Tibetan, Noto Sans Yi"); +pref("font.name-list.monospace.x-unicode", "Menlo, Courier, Arial, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongolian, Noto Sans Myanmar, Noto Sans Oriya, Noto Sans Sinhala, Noto Sans Tamil, Noto Sans Telugu, Noto Sans Thaana, Noto Sans Tibetan, Noto Sans Yi"); +pref("font.name-list.sans-serif.x-unicode", "Helvetica, Tahoma, Arial, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongolian, Noto Sans Myanmar, Noto Sans Oriya, Noto Sans Sinhala, Noto Sans Tamil, Noto Sans Telugu, Noto Sans Thaana, Noto Sans Tibetan, Noto Sans Yi"); +pref("font.name-list.serif.x-unicode", "Times, Arial, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongolian, Noto Sans Myanmar, Noto Sans Oriya, Noto Sans Sinhala, Noto Sans Tamil, Noto Sans Telugu, Noto Sans Thaana, Noto Sans Tibetan, Noto Sans Yi"); +pref("font.name.cursive.ar", "Arial"); +pref("font.name.fantasy.ar", "Arial"); +pref("font.name.monospace.ar", "Arial"); +pref("font.name.sans-serif.ar", "Arial"); +#endif + +#ifdef XP_WIN +pref("font.system.whitelist", "Arial, Batang, 바탕, Cambria Math, Courier New, Euphemia, Gautami, Georgia, Gulim, 굴림, GulimChe, 굴림체, Iskoola Pota, Kalinga, Kartika, Latha, Lucida Console, MS Gothic, MS ゴシック, MS Mincho, MS 明朝, MS PGothic, MS Pゴシック, MS PMincho, MS P明朝, MV Boli, Malgun Gothic, Mangal, Meiryo, Meiryo UI, Microsoft Himalaya, Microsoft JhengHei, Microsoft JhengHei UI, Microsoft YaHei, 微软雅黑, Microsoft YaHei UI, MingLiU, 細明體, Noto Sans Buginese, Noto Sans Khmer, Noto Sans Lao, Not [...] +#endif + +#ifdef XP_LINUX +pref("font.default.lo", "Noto Sans Lao"); +pref("font.default.my", "Noto Sans Myanmar"); +pref("font.default.x-western", "sans-serif"); +pref("font.name-list.cursive.ar", "Noto Naskh Arabic, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.cursive.he", "Noto Sans Hebrew, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.cursive.x-cyrillic", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.cursive.x-unicode", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.cursive.x-western", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.fantasy.ar", "Noto Naskh Arabic, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.fantasy.el", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.fantasy.he", "Noto Sans Hebrew, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.fantasy.x-cyrillic", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.fantasy.x-unicode", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.fantasy.x-western", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.monospace.ar", "Noto Naskh Arabic, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayal [...] +pref("font.name-list.monospace.el", "Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongo [...] +pref("font.name-list.monospace.he", "Noto Sans Hebrew, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayala [...] +pref("font.name-list.monospace.ja", "Noto Sans JP Regular, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mala [...] +pref("font.name-list.monospace.ko", "Noto Sans KR Regular, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mala [...] +pref("font.name-list.monospace.th", "Noto Sans Thai, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.monospace.x-armn", "Noto Sans Armenian, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Ma [...] +pref("font.name-list.monospace.x-beng", "Noto Sans Bengali, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mal [...] +pref("font.name-list.monospace.x-cyrillic", "Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sa [...] +pref("font.name-list.monospace.x-devanagari", "Noto Sans Devanagari, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto [...] +pref("font.name-list.monospace.x-ethi", "Noto Sans Ethiopic, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Ma [...] +pref("font.name-list.monospace.x-geor", "Noto Sans Georgian, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Ma [...] +pref("font.name-list.monospace.x-gujr", "Noto Sans Gujarati, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Ma [...] +pref("font.name-list.monospace.x-guru", "Noto Sans Gurmukhi, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Ma [...] +pref("font.name-list.monospace.x-khmr", "Noto Sans Khmer, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malay [...] +pref("font.name-list.monospace.x-knda", "Noto Sans Kannada, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mal [...] +pref("font.name-list.monospace.x-mlym", "Noto Sans Malayalam, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans M [...] +pref("font.name-list.monospace.x-orya", "Noto Sans Oriya, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malay [...] +pref("font.name-list.monospace.x-sinh", "Noto Sans Sinhala, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mal [...] +pref("font.name-list.monospace.x-tamil", "Noto Sans Tamil, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mala [...] +pref("font.name-list.monospace.x-telu", "Noto Sans Telugu, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mala [...] +pref("font.name-list.monospace.x-tibt", "Noto Sans Tibetan, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Mal [...] +pref("font.name-list.monospace.x-unicode", "Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto San [...] +pref("font.name-list.monospace.x-western", "Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto San [...] +pref("font.name-list.monospace.zh-CN", "Noto Sans SC Regular, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans M [...] +pref("font.name-list.monospace.zh-HK", "Noto Sans TC Regular, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans M [...] +pref("font.name-list.monospace.zh-TW", "Noto Sans TC Regular, Cousine, Courier, Courier New, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans M [...] +pref("font.name-list.sans-serif.ar", "Noto Naskh Arabic, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Not [...] +pref("font.name-list.sans-serif.el", "Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongolian, N [...] +pref("font.name-list.sans-serif.he", "Noto Sans Hebrew, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto [...] +pref("font.name-list.sans-serif.ja", "Noto Sans JP Regular, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.ko", "Noto Sans KR Regular, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.th", "Noto Sans Thai, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto S [...] +pref("font.name-list.sans-serif.x-armn", "Noto Sans Armenian, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam [...] +pref("font.name-list.sans-serif.x-beng", "Noto Sans Bengali, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.x-cyrillic", "Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mong [...] +pref("font.name-list.sans-serif.x-devanagari", "Noto Sans Devanagari, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans M [...] +pref("font.name-list.sans-serif.x-ethi", "Noto Sans Ethiopic, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam [...] +pref("font.name-list.sans-serif.x-geor", "Noto Sans Georgian, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam [...] +pref("font.name-list.sans-serif.x-gujr", "Noto Sans Gujarati, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam [...] +pref("font.name-list.sans-serif.x-guru", "Noto Sans Gurmukhi, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam [...] +pref("font.name-list.sans-serif.x-khmr", "Noto Sans Khmer, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, N [...] +pref("font.name-list.sans-serif.x-knda", "Noto Sans Kannada, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.x-mlym", "Noto Sans Malayalam, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayala [...] +pref("font.name-list.sans-serif.x-orya", "Noto Sans Oriya, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, N [...] +pref("font.name-list.sans-serif.x-sinh", "Noto Sans Sinhala, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.x-tamil", "Noto Sans Tamil, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.x-telu", "Noto Sans Telugu, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.x-tibt", "Noto Sans Tibetan, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, [...] +pref("font.name-list.sans-serif.x-unicode", "Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongo [...] +pref("font.name-list.sans-serif.x-western", "Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayalam, Noto Sans Mongo [...] +pref("font.name-list.sans-serif.zh-CN", "Noto Sans SC Regular, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayala [...] +pref("font.name-list.sans-serif.zh-HK", "Noto Sans TC Regular, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayala [...] +pref("font.name-list.sans-serif.zh-TW", "Noto Sans TC Regular, Arimo, Arial, Verdana, Noto Naskh Arabic, Noto Sans Armenian, Noto Sans Bengali, Noto Sans Buginese, Noto Sans JP Regular, Noto Sans KR Regular, Noto Sans SC Regular, Noto Sans TC Regular, Noto Sans Canadian Aboriginal, Noto Sans Cherokee, Noto Sans Devanagari, Noto Sans Ethiopic, Noto Sans Georgian, Noto Sans Gujarati, Noto Sans Gurmukhi, Noto Sans Hebrew, Noto Sans Kannada, Noto Sans Khmer, Noto Sans Lao, Noto Sans Malayala [...] +pref("font.name-list.serif.ar", "Noto Naskh Arabic, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.el", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.he", "Tinos, Georgia, Noto Sans Hebrew, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.ja", "Noto Sans JP Regular, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.ko", "Noto Sans KR Regular, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.th", "Noto Serif Thai, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-armn", "Noto Serif Armenian, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-beng", "Noto Sans Bengali, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-cyrillic", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-devanagari", "Noto Sans Devanagari, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-ethi", "Noto Sans Ethiopic, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-geor", "Noto Sans Georgian, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-gujr", "Noto Sans Gujarati, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-guru", "Noto Sans Gurmukhi, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-khmr", "Noto Serif Khmer, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-knda", "Noto Sans Kannada, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-mlym", "Noto Sans Malayalam, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-orya", "Noto Sans Oriya, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-sinh", "Noto Sans Sinhala, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-tamil", "Noto Sans Tamil, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-telu", "Noto Sans Telugu, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-tibt", "Noto Sans Tibetan, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-unicode", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.x-western", "Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.zh-CN", "Noto Sans SC Regular, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.zh-HK", "Noto Sans TC Regular, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name-list.serif.zh-TW", "Noto Sans TC Regular, Tinos, Georgia, Noto Serif Armenian, Noto Serif Khmer, Noto Serif Lao, Noto Serif Thai"); +pref("font.name.cursive.ar", "Noto Naskh Arabic"); +pref("font.name.cursive.el", "Tinos, Georgia"); +pref("font.name.cursive.he", "Noto Sans Hebrew"); +pref("font.name.cursive.x-cyrillic", "Tinos, Georgia"); +pref("font.name.cursive.x-unicode", "Tinos, Georgia"); +pref("font.name.cursive.x-western", "Tinos, Georgia"); +pref("font.name.fantasy.ar", "Noto Naskh Arabic"); +pref("font.name.fantasy.el", "Tinos, Georgia"); +pref("font.name.fantasy.he", "Noto Sans Hebrew"); +pref("font.name.fantasy.x-cyrillic", "Tinos, Georgia"); +pref("font.name.fantasy.x-unicode", "Tinos, Georgia"); +pref("font.name.fantasy.x-western", "Tinos, Georgia"); +pref("font.name.monospace.ar", "Noto Naskh Arabic"); +pref("font.name.monospace.el", "Tinos, Georgia"); +pref("font.name.monospace.he", "Noto Sans Hebrew"); +pref("font.name.monospace.ja", "Noto Sans JP Regular"); +pref("font.name.monospace.ko", "Noto Sans KR Regular"); +pref("font.name.monospace.my", "Noto Sans Myanmar"); +pref("font.name.monospace.th", "Noto Sans Thai"); +pref("font.name.monospace.x-armn", "Noto Sans Armenian"); +pref("font.name.monospace.x-beng", "Noto Sans Bengali"); +pref("font.name.monospace.x-cyrillic", "Cousine, Courier, Courier New"); +pref("font.name.monospace.x-devanagari", "Noto Sans Devanagari"); +pref("font.name.monospace.x-ethi", "Noto Sans Ethiopic"); +pref("font.name.monospace.x-geor", "Noto Sans Georgian"); +pref("font.name.monospace.x-gujr", "Noto Sans Gujarati"); +pref("font.name.monospace.x-guru", "Noto Sans Gurmukhi"); +pref("font.name.monospace.x-khmr", "Noto Sans Khmer"); +pref("font.name.monospace.x-knda", "Noto Sans Kannada"); +pref("font.name.monospace.x-mlym", "Noto Sans Malayalam"); +pref("font.name.monospace.x-orya", "Noto Sans Oriya"); +pref("font.name.monospace.x-sinh", "Noto Sans Sinhala"); +pref("font.name.monospace.x-tamil", "Noto Sans Tamil"); +pref("font.name.monospace.x-telu", "Noto Sans Telugu"); +pref("font.name.monospace.x-tibt", "Noto Sans Tibetan"); +pref("font.name.monospace.x-unicode", "Cousine, Courier, Courier New"); +pref("font.name.monospace.x-western", "Cousine, Courier, Courier New"); +pref("font.name.monospace.zh-CN", "Noto Sans SC Regular"); +pref("font.name.monospace.zh-HK", "Noto Sans TC Regular"); +pref("font.name.monospace.zh-TW", "Noto Sans TC Regular"); +pref("font.name.sans-serif.ar", "Noto Naskh Arabic"); +pref("font.name.sans-serif.el", "Arimo, Arial, Verdana"); +pref("font.name.sans-serif.he", "Noto Sans Hebrew"); +pref("font.name.sans-serif.ja", "Noto Sans JP Regular"); +pref("font.name.sans-serif.ko", "Noto Sans KR Regular"); +pref("font.name.sans-serif.th", "Noto Sans Thai"); +pref("font.name.sans-serif.x-armn", "Noto Sans Armenian"); +pref("font.name.sans-serif.x-beng", "Noto Sans Bengali"); +pref("font.name.sans-serif.x-cyrillic", "Arimo, Arial, Verdana"); +pref("font.name.sans-serif.x-devanagari", "Noto Sans Devanagari"); +pref("font.name.sans-serif.x-ethi", "Noto Sans Ethiopic"); +pref("font.name.sans-serif.x-geor", "Noto Sans Georgian"); +pref("font.name.sans-serif.x-gujr", "Noto Sans Gujarati"); +pref("font.name.sans-serif.x-guru", "Noto Sans Gurmukhi"); +pref("font.name.sans-serif.x-khmr", "Noto Sans Khmer"); +pref("font.name.sans-serif.x-knda", "Noto Sans Kannada"); +pref("font.name.sans-serif.x-mlym", "Noto Sans Malayalam"); +pref("font.name.sans-serif.x-orya", "Noto Sans Oriya"); +pref("font.name.sans-serif.x-sinh", "Noto Sans Sinhala"); +pref("font.name.sans-serif.x-tamil", "Noto Sans Tamil"); +pref("font.name.sans-serif.x-telu", "Noto Sans Telugu"); +pref("font.name.sans-serif.x-tibt", "Noto Sans Tibetan"); +pref("font.name.sans-serif.x-unicode", "Arimo, Arial, Verdana"); +pref("font.name.sans-serif.x-western", "Arimo, Arial, Verdana"); +pref("font.name.sans-serif.zh-CN", "Noto Sans SC Regular"); +pref("font.name.sans-serif.zh-HK", "Noto Sans TC Regular"); +pref("font.name.sans-serif.zh-TW", "Noto Sans TC Regular"); +pref("font.name.sans.my", "Noto Sans Myanmar"); +pref("font.name.serif.ar", "Noto Naskh Arabic"); +pref("font.name.serif.el", "Tinos, Georgia"); +pref("font.name.serif.he", "Noto Sans Hebrew"); +pref("font.name.serif.ja", "Noto Sans JP Regular"); +pref("font.name.serif.ko", "Noto Sans KR Regular"); +pref("font.name.serif.my", "Noto Sans Myanmar"); +pref("font.name.serif.th", "Noto Serif Thai"); +pref("font.name.serif.x-armn", "Noto Serif Armenian"); +pref("font.name.serif.x-beng", "Noto Sans Bengali"); +pref("font.name.serif.x-cyrillic", "Tinos, Georgia"); +pref("font.name.serif.x-devanagari", "Noto Sans Devanagari"); +pref("font.name.serif.x-ethi", "Noto Sans Ethiopic"); +pref("font.name.serif.x-geor", "Noto Sans Georgian"); +pref("font.name.serif.x-gujr", "Noto Sans Gujarati"); +pref("font.name.serif.x-guru", "Noto Sans Gurmukhi"); +pref("font.name.serif.x-khmr", "Noto Serif Khmer"); +pref("font.name.serif.x-knda", "Noto Sans Kannada"); +pref("font.name.serif.x-mlym", "Noto Sans Malayalam"); +pref("font.name.serif.x-orya", "Noto Sans Oriya"); +pref("font.name.serif.x-sinh", "Noto Sans Sinhala"); +pref("font.name.serif.x-tamil", "Noto Sans Tamil"); +pref("font.name.serif.x-telu", "Noto Sans Telugu"); +pref("font.name.serif.x-tibt", "Noto Sans Tibetan"); +pref("font.name.serif.x-unicode", "Tinos, Georgia"); +pref("font.name.serif.x-western", "Tinos, Georgia"); +pref("font.name.serif.zh-CN", "Noto Sans SC Regular"); +pref("font.name.serif.zh-HK", "Noto Sans TC Regular"); +pref("font.name.serif.zh-TW", "Noto Sans TC Regular"); +#endif +#endif diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index fba09ecef7189..8ace92e9bf07f 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -51,9 +51,9 @@ pref("extensions.recommendations.themeRecommendationUrl", "https://color.firefox
pref("extensions.update.autoUpdateDefault", true);
-// Check AUS for system add-on updates. -pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_T..."); -pref("extensions.systemAddon.update.enabled", true); +// No AUS check for system add-on updates for Tor Browser users. +pref("extensions.systemAddon.update.url", ""); +pref("extensions.systemAddon.update.enabled", false);
// Disable add-ons that are not installed by the user in all scopes by default. // See the SCOPE constants in AddonManager.jsm for values to use here. diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 26f093c500645..da8cfeca1a649 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -282,6 +282,7 @@ @RESPATH@/browser/defaults/settings/blocklists @RESPATH@/browser/defaults/settings/main @RESPATH@/browser/defaults/settings/security-state +@RESPATH@/browser/@PREF_DIR@/001-base-profile.js
; Warning: changing the path to channel-prefs.js can cause bugs (Bug 756325) ; Technically this is an app pref file, but we are keeping it in the original diff --git a/browser/moz.build b/browser/moz.build index 7b5566ac5de77..0df0a824f9ad4 100644 --- a/browser/moz.build +++ b/browser/moz.build @@ -56,6 +56,7 @@ if CONFIG["MOZ_UPDATE_AGENT"]: # These files are specified in this moz.build to pick up DIST_SUBDIR as set in # this directory, which is un-set in browser/app. JS_PREFERENCE_PP_FILES += [ + "app/profile/001-base-profile.js", "app/profile/firefox.js", ] FINAL_TARGET_FILES.defaults += ["app/permissions"] diff --git a/browser/themes/shared/menupanel.inc.css b/browser/themes/shared/menupanel.inc.css index 4629e5e868912..5fee092863979 100644 --- a/browser/themes/shared/menupanel.inc.css +++ b/browser/themes/shared/menupanel.inc.css @@ -23,3 +23,4 @@ #appMenu-fullscreen-button2[checked] { list-style-image: url(chrome://browser/skin/fullscreen-exit.svg); } + diff --git a/mobile/android/app/geckoview-prefs.js b/mobile/android/app/geckoview-prefs.js index d16b3e75169ee..b6035bdc40f39 100644 --- a/mobile/android/app/geckoview-prefs.js +++ b/mobile/android/app/geckoview-prefs.js @@ -98,3 +98,5 @@ pref("extensions.formautofill.addresses.capture.enabled", true); // Debug prefs. pref("browser.formfill.debug", false); pref("extensions.formautofill.loglevel", "Warn"); + +#include 000-tor-browser-android.js diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 3d0b2e8c020f7..a1703b7594058 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -365,7 +365,11 @@ pref("app.update.timerMinimumDelay", 30); // seconds // used by update service to decide whether or not to // automatically download an update pref("app.update.autodownload", "wifi"); +#ifdef TOR_BROWSER_VERSION +pref("app.update.url.android", ""); +#else pref("app.update.url.android", "https://aus5.mozilla.org/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARG..."); +#endif
#ifdef MOZ_UPDATER /* prefs used specifically for updating the app */ diff --git a/mobile/android/app/moz.build b/mobile/android/app/moz.build index 21fa8617c5ff9..4686e3df08b8f 100644 --- a/mobile/android/app/moz.build +++ b/mobile/android/app/moz.build @@ -17,6 +17,7 @@ if CONFIG["MOZ_PKG_SPECIAL"]: DEFINES["MOZ_PKG_SPECIAL"] = CONFIG["MOZ_PKG_SPECIAL"]
JS_PREFERENCE_PP_FILES += [ + "000-tor-browser-android.js", "mobile.js", ]
diff --git a/taskcluster/ci/source-test/mozlint.yml b/taskcluster/ci/source-test/mozlint.yml index 59cceb4900bb9..589ad7a0af1e4 100644 --- a/taskcluster/ci/source-test/mozlint.yml +++ b/taskcluster/ci/source-test/mozlint.yml @@ -163,6 +163,7 @@ lintpref: files-changed: - 'modules/libpref/init/all.js' - 'modules/libpref/init/StaticPrefList.yaml' + - 'browser/app/profile/001-base-profile.js' - 'browser/app/profile/firefox.js' - 'mobile/android/app/mobile.js' - 'devtools/client/preferences/debugger.js'
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 4a967032d1011e7a83811017fff1506bf2c6836a Author: Matthew Finkel sysrqb@torproject.org AuthorDate: Mon Sep 14 02:52:28 2020 +0000
Bug 40125: Expose Security Level pref in GeckoView --- mobile/android/geckoview/api.txt | 3 ++ .../mozilla/geckoview/GeckoRuntimeSettings.java | 33 ++++++++++++++++++++++ 2 files changed, 36 insertions(+)
diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 5a04541ae57fd..f9f0c9d14a8ac 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -738,6 +738,7 @@ package org.mozilla.geckoview { method @Nullable public GeckoRuntime getRuntime(); method @Nullable public Rect getScreenSizeOverride(); method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate(); + method public int getTorSecurityLevel(); method public boolean getUseMaxScreenDepth(); method public boolean getWebFontsEnabled(); method public boolean getWebManifestEnabled(); @@ -758,6 +759,7 @@ package org.mozilla.geckoview { method @NonNull public GeckoRuntimeSettings setLoginAutofillEnabled(boolean); method @NonNull public GeckoRuntimeSettings setPreferredColorScheme(int); method @NonNull public GeckoRuntimeSettings setRemoteDebuggingEnabled(boolean); + method @NonNull public GeckoRuntimeSettings setTorSecurityLevel(int); method @NonNull public GeckoRuntimeSettings setWebFontsEnabled(boolean); method @NonNull public GeckoRuntimeSettings setWebManifestEnabled(boolean); field public static final int ALLOW_ALL = 0; @@ -798,6 +800,7 @@ package org.mozilla.geckoview { method @NonNull public GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean); method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int); method @NonNull public GeckoRuntimeSettings.Builder telemetryDelegate(@NonNull RuntimeTelemetry.Delegate); + method @NonNull public GeckoRuntimeSettings.Builder torSecurityLevel(int); method @NonNull public GeckoRuntimeSettings.Builder useMaxScreenDepth(boolean); method @NonNull public GeckoRuntimeSettings.Builder webFontsEnabled(boolean); method @NonNull public GeckoRuntimeSettings.Builder webManifest(boolean); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java index 5de0b00f7a0cc..c573ee7688f32 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -472,6 +472,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { getSettings().setAllowInsecureConnections(level); return this; } + + /** + * Set security level. + * + * @param level A value determining the security level. Default is 0. + * @return This Builder instance. + */ + public @NonNull Builder torSecurityLevel(final int level) { + getSettings().mTorSecurityLevel.set(level); + return this; + } }
private GeckoRuntime mRuntime; @@ -528,6 +539,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { "dom.security.https_only_mode_pbm", false); /* package */ final Pref<Integer> mProcessCount = new Pref<>( "dom.ipc.processCount", 2); + /* package */ final Pref<Integer> mTorSecurityLevel = new Pref<>( + "extensions.torbutton.security_slider", 4);
/* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
@@ -1280,6 +1293,26 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { return this; }
+ /** + * Gets the current security level. + * + * @return current security protection level + */ + public int getTorSecurityLevel() { + return mTorSecurityLevel.get(); + } + + /** + * Sets the Tor Security Level. + * + * @param level security protection level + * @return This GeckoRuntimeSettings instance. + */ + public @NonNull GeckoRuntimeSettings setTorSecurityLevel(final int level) { + mTorSecurityLevel.commit(level); + return this; + } + @Override // Parcelable public void writeToParcel(final Parcel out, final int flags) { super.writeToParcel(out, flags);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 33199ea462fb0c71609a1abd4d724b7fe8caa4de Author: Alex Catarineu acat@torproject.org AuthorDate: Fri Oct 16 10:45:17 2020 +0200
Bug 30605: Honor privacy.spoof_english in Android
This checks `privacy.spoof_english` whenever `setLocales` is called from Fenix side and sets `intl.accept_languages` accordingly. --- mobile/android/components/geckoview/GeckoViewStartup.jsm | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/mobile/android/components/geckoview/GeckoViewStartup.jsm b/mobile/android/components/geckoview/GeckoViewStartup.jsm index 055c3da638e14..2bf394f2cb3b9 100644 --- a/mobile/android/components/geckoview/GeckoViewStartup.jsm +++ b/mobile/android/components/geckoview/GeckoViewStartup.jsm @@ -17,6 +17,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { EventDispatcher: "resource://gre/modules/Messaging.jsm", Preferences: "resource://gre/modules/Preferences.jsm", Services: "resource://gre/modules/Services.jsm", + RFPHelper: "resource://gre/modules/RFPHelper.jsm", });
const { debug, warn } = GeckoViewUtils.initLogging("Startup"); @@ -255,6 +256,10 @@ class GeckoViewStartup { if (aData.requestedLocales) { Services.locale.requestedLocales = aData.requestedLocales; } + RFPHelper._handleSpoofEnglishChanged(); + if (Services.prefs.getIntPref("privacy.spoof_english", 0) === 2) { + break; + } const pls = Cc["@mozilla.org/pref-localizedstring;1"].createInstance( Ci.nsIPrefLocalizedString );
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 8a60de0ee4edcb5a5c949cfde8b3059719de7d16 Author: Alex Catarineu acat@torproject.org AuthorDate: Tue Oct 20 17:44:36 2020 +0200
Bug 40199: Avoid using system locale for intl.accept_languages in GeckoView --- .../mozilla/geckoview/GeckoRuntimeSettings.java | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java index c573ee7688f32..d88e296d554ae 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -821,19 +821,25 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { private String computeAcceptLanguages() { final ArrayList<String> locales = new ArrayList<String>();
- // Explicitly-set app prefs come first: - if (mRequestedLocales != null) { - for (final String locale : mRequestedLocales) { - locales.add(locale.toLowerCase(Locale.ROOT)); - } - } - // OS prefs come second: - for (final String locale : getDefaultLocales()) { - final String localeLowerCase = locale.toLowerCase(Locale.ROOT); - if (!locales.contains(localeLowerCase)) { - locales.add(localeLowerCase); + // In Desktop, these are defined in the `intl.accept_languages` localized property. + // At some point we should probably use the same values here, but for now we use a simple + // strategy which will hopefully result in reasonable acceptLanguage values. + if (mRequestedLocales != null && mRequestedLocales.length > 0) { + String locale = mRequestedLocales[0].toLowerCase(Locale.ROOT); + // No need to include `en-us` twice. + if (!locale.equals("en-us")) { + locales.add(locale); + if (locale.contains("-")) { + String lang = locale.split("-")[0]; + // No need to include `en` twice. + if (!lang.equals("en")) { + locales.add(lang); + } + } } } + locales.add("en-us"); + locales.add("en");
return TextUtils.join(",", locales); }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit fe47e4626b8df26073aa71b8f0fca45676af4629 Author: Alex Catarineu acat@torproject.org AuthorDate: Sun Oct 18 17:06:04 2020 +0200
Bug 40198: Expose privacy.spoof_english pref in GeckoView --- mobile/android/geckoview/api.txt | 3 ++ .../mozilla/geckoview/GeckoRuntimeSettings.java | 33 ++++++++++++++++++++++ 2 files changed, 36 insertions(+)
diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index f9f0c9d14a8ac..3335aaffb1fb0 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -737,6 +737,7 @@ package org.mozilla.geckoview { method public boolean getRemoteDebuggingEnabled(); method @Nullable public GeckoRuntime getRuntime(); method @Nullable public Rect getScreenSizeOverride(); + method public boolean getSpoofEnglish(); method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate(); method public int getTorSecurityLevel(); method public boolean getUseMaxScreenDepth(); @@ -759,6 +760,7 @@ package org.mozilla.geckoview { method @NonNull public GeckoRuntimeSettings setLoginAutofillEnabled(boolean); method @NonNull public GeckoRuntimeSettings setPreferredColorScheme(int); method @NonNull public GeckoRuntimeSettings setRemoteDebuggingEnabled(boolean); + method @NonNull public GeckoRuntimeSettings setSpoofEnglish(boolean); method @NonNull public GeckoRuntimeSettings setTorSecurityLevel(int); method @NonNull public GeckoRuntimeSettings setWebFontsEnabled(boolean); method @NonNull public GeckoRuntimeSettings setWebManifestEnabled(boolean); @@ -799,6 +801,7 @@ package org.mozilla.geckoview { method @NonNull public GeckoRuntimeSettings.Builder preferredColorScheme(int); method @NonNull public GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean); method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int); + method @NonNull public GeckoRuntimeSettings.Builder spoofEnglish(boolean); method @NonNull public GeckoRuntimeSettings.Builder telemetryDelegate(@NonNull RuntimeTelemetry.Delegate); method @NonNull public GeckoRuntimeSettings.Builder torSecurityLevel(int); method @NonNull public GeckoRuntimeSettings.Builder useMaxScreenDepth(boolean); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java index d88e296d554ae..5b54447cb6e62 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -483,6 +483,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { getSettings().mTorSecurityLevel.set(level); return this; } + + /** + * Sets whether we should spoof locale to English for webpages. + * + * @param flag True if we should spoof locale to English for webpages, false otherwise. + * @return This Builder instance. + */ + public @NonNull Builder spoofEnglish(final boolean flag) { + getSettings().mSpoofEnglish.set(flag ? 2 : 1); + return this; + } }
private GeckoRuntime mRuntime; @@ -541,6 +552,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { "dom.ipc.processCount", 2); /* package */ final Pref<Integer> mTorSecurityLevel = new Pref<>( "extensions.torbutton.security_slider", 4); + /* package */ final Pref<Integer> mSpoofEnglish = new Pref<>( + "privacy.spoof_english", 0);
/* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
@@ -1319,6 +1332,26 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { return this; }
+ /** + * Get whether we should spoof locale to English for webpages. + * + * @return Whether we should spoof locale to English for webpages. + */ + public boolean getSpoofEnglish() { + return mSpoofEnglish.get() == 2; + } + + /** + * Set whether we should spoof locale to English for webpages. + * + * @param flag A flag determining whether we should locale to English for webpages. + * @return This GeckoRuntimeSettings instance. + */ + public @NonNull GeckoRuntimeSettings setSpoofEnglish(final boolean flag) { + mSpoofEnglish.commit(flag ? 2 : 1); + return this; + } + @Override // Parcelable public void writeToParcel(final Parcel out, final int flags) { super.writeToParcel(out, flags);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit e1ed71fbd2e902160c92ce0fcfde9e6b0965bfcb Author: Alex Catarineu acat@torproject.org AuthorDate: Wed Nov 4 15:58:22 2020 +0100
Bug 40171: Make WebRequest and GeckoWebExecutor First-Party aware --- .../main/java/org/mozilla/geckoview/WebRequest.java | 18 ++++++++++++++++++ widget/android/WebExecutorSupport.cpp | 10 ++++++++++ 2 files changed, 28 insertions(+)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java index d1d6e06b73967..4e17bc034edbd 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java @@ -51,6 +51,11 @@ public class WebRequest extends WebMessage { */ public final @Nullable String referrer;
+ /** + * The value of the origin of this request. + */ + public final @Nullable String origin; + @Retention(RetentionPolicy.SOURCE) @IntDef({CACHE_MODE_DEFAULT, CACHE_MODE_NO_STORE, CACHE_MODE_RELOAD, CACHE_MODE_NO_CACHE, @@ -112,6 +117,7 @@ public class WebRequest extends WebMessage { method = builder.mMethod; cacheMode = builder.mCacheMode; referrer = builder.mReferrer; + origin = builder.mOrigin;
if (builder.mBody != null) { body = builder.mBody.asReadOnlyBuffer(); @@ -128,6 +134,7 @@ public class WebRequest extends WebMessage { /* package */ String mMethod = "GET"; /* package */ int mCacheMode = CACHE_MODE_DEFAULT; /* package */ String mReferrer; + /* package */ String mOrigin;
/** * Construct a Builder instance with the specified URI. @@ -226,6 +233,17 @@ public class WebRequest extends WebMessage { return this; }
+ /** + * Set the origin URI. + * + * @param origin A URI String + * @return This Builder instance. + */ + public @NonNull Builder origin(final @Nullable String origin) { + mOrigin = origin; + return this; + } + /** * @return A {@link WebRequest} constructed with the values from this Builder instance. */ diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp index 99e7de95a0fb6..bbdcc8f36bd6f 100644 --- a/widget/android/WebExecutorSupport.cpp +++ b/widget/android/WebExecutorSupport.cpp @@ -393,6 +393,16 @@ nsresult WebExecutorSupport::CreateStreamLoader( MOZ_ASSERT(cookieJarSettings);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + + RefPtr<nsIURI> originUri; + const auto origin = req->Origin(); + if (origin) { + rv = NS_NewURI(getter_AddRefs(originUri), origin->ToString()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + OriginAttributes attrs = loadInfo->GetOriginAttributes(); + attrs.SetFirstPartyDomain(true, originUri); + loadInfo->SetOriginAttributes(attrs); + } loadInfo->SetCookieJarSettings(cookieJarSettings);
// setup http/https specific things
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 9d6721506b5e78ae6dc5f9ee64d1a62564d2f78e Author: Alex Catarineu acat@torproject.org AuthorDate: Tue Sep 10 16:29:31 2019 +0200
Bug 26345: Hide tracking protection UI --- browser/base/content/appmenu-viewcache.inc.xhtml | 4 ++-- browser/base/content/browser-siteIdentity.js | 4 ++-- browser/components/about/AboutRedirector.cpp | 4 ---- browser/components/about/components.conf | 1 - browser/components/moz.build | 1 - browser/themes/shared/preferences/privacy.css | 4 ++++ 6 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml index 204b84f00000f..895ef976fc23c 100644 --- a/browser/base/content/appmenu-viewcache.inc.xhtml +++ b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -24,7 +24,7 @@ oncommand="gSync.toggleAccountPanel(this, event)"/> </toolbaritem> <toolbarseparator class="sync-ui-item"/> - <toolbaritem> + <toolbaritem hidden="true"> <toolbarbutton id="appMenu-protection-report-button" class="subviewbutton subviewbutton-iconic" oncommand="gProtectionsHandler.openProtections(); gProtectionsHandler.recordClick('open_full_report', null, 'app_menu');"> @@ -35,7 +35,7 @@ </label> </toolbarbutton> </toolbaritem> - <toolbarseparator id="appMenu-tp-separator"/> + <toolbarseparator hidden="true" id="appMenu-tp-separator"/> <toolbarbutton id="appMenu-new-window-button" class="subviewbutton subviewbutton-iconic" data-l10n-id="appmenuitem-new-window" diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 8c6d1e20ddef7..2846a1cb2fcfd 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -908,10 +908,10 @@ var gIdentityHandler = { gPermissionPanel.refreshPermissionIcons(); }
- // Hide the shield icon if it is a chrome page. + // Bug 26345: Hide tracking protection UI. gProtectionsHandler._trackingProtectionIconContainer.classList.toggle( "chromeUI", - this._isSecureInternalUI + true ); },
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 2ace276cd50c1..6d283fe67b206 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -122,10 +122,6 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::HIDE_FROM_ABOUTABOUT}, {"restartrequired", "chrome://browser/content/aboutRestartRequired.xhtml", nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, - {"protections", "chrome://browser/content/protections.html", - nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 6fd827dead373..8ce22e9cff519 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -19,7 +19,6 @@ pages = [ 'policies', 'preferences', 'privatebrowsing', - 'protections', 'profiling', 'reader', 'restartrequired', diff --git a/browser/components/moz.build b/browser/components/moz.build index 5f8780e01c65a..1bc09f4093fb7 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -45,7 +45,6 @@ DIRS += [ "preferences", "privatebrowsing", "prompts", - "protections", "protocolhandler", "resistfingerprinting", "search", diff --git a/browser/themes/shared/preferences/privacy.css b/browser/themes/shared/preferences/privacy.css index b55c242b4c05d..154222f84b11a 100644 --- a/browser/themes/shared/preferences/privacy.css +++ b/browser/themes/shared/preferences/privacy.css @@ -77,6 +77,10 @@
/* Content Blocking */
+#trackingGroup { + display: none; +} + /* Override styling that sets descriptions as grey */ #trackingGroup description.indent, #trackingGroup .indent > description {
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 21cf79937a4d684ea6c14cbe5e9d3eacc247066a Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Fri Oct 18 15:20:06 2013 -0400
Bug 9173: Change the default Firefox profile directory to be TBB-relative.
This should eliminate our need to rely on a wrapper script that sets /Users/arthur and launches Firefox with -profile. --- browser/config/mozconfigs/base-browser | 2 + moz.configure | 19 ++++++++ toolkit/xre/nsAppRunner.cpp | 14 ++++-- toolkit/xre/nsXREDirProvider.cpp | 85 +++++++++++++++------------------- toolkit/xre/nsXREDirProvider.h | 8 ++++ xpcom/io/nsAppFileLocationProvider.cpp | 61 +++++++++++++----------- 6 files changed, 110 insertions(+), 79 deletions(-)
diff --git a/browser/config/mozconfigs/base-browser b/browser/config/mozconfigs/base-browser index eb495fd0df948..9963ab7ef9e62 100644 --- a/browser/config/mozconfigs/base-browser +++ b/browser/config/mozconfigs/base-browser @@ -29,3 +29,5 @@ ac_add_options --enable-proxy-bypass-protection
# Disable telemetry ac_add_options MOZ_TELEMETRY_REPORTING= + +ac_add_options --with-relative-profile=BaseBrowser/Data/Browser diff --git a/moz.configure b/moz.configure index 089b5489ffcda..244a0c775d098 100755 --- a/moz.configure +++ b/moz.configure @@ -1117,6 +1117,25 @@ set_config("MOZ_SYSTEM_ZLIB", True, when="--with-system-zlib") add_old_configure_assignment("MOZ_SYSTEM_ZLIB", True, when="--with-system-zlib")
+option( + "--with-relative-profile", + nargs=1, + help="Sets the directory of the profile, relative to the application directory" +) + + +@depends("--with-relative-profile", target) +@imports("json") +def relative_profile(value, target): + if target.os == "Android": + die("--with-relative-profile is not supported on Android") + if value: + return json.dumps(value[0]) + + +set_define("RELATIVE_PROFILE_DIRECTORY", relative_profile) + + # Please do not add configure checks from here on.
# Fallthrough to autoconf-based configure diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index d3bb8096ad02e..0d4826fa0b37d 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2368,6 +2368,8 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) { } }
+// If aUnlocker is NULL, it is also OK for the following arguments to be NULL: +// aProfileDir, aProfileLocalDir, aResult. static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir, nsIFile* aProfileLocalDir, nsIProfileUnlocker* aUnlocker, @@ -2375,17 +2377,19 @@ static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir, nsIProfileLock** aResult) { nsresult rv;
- bool exists; - aProfileDir->Exists(&exists); - if (!exists) { - return ProfileMissingDialog(aNative); + if (aProfileDir) { + bool exists; + aProfileDir->Exists(&exists); + if (!exists) { + return ProfileMissingDialog(aNative); + } }
ScopedXPCOMStartup xpcom; rv = xpcom.Initialize(); NS_ENSURE_SUCCESS(rv, rv);
- mozilla::Telemetry::WriteFailedProfileLock(aProfileDir); + if (aProfileDir) mozilla::Telemetry::WriteFailedProfileLock(aProfileDir);
rv = xpcom.SetWindowCreator(aNative); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp index d6def8aee83d6..4360b23c820fe 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -255,9 +255,6 @@ nsresult nsXREDirProvider::GetUserProfilesRootDir(nsIFile** aResult) { nsresult rv = GetUserDataDirectory(getter_AddRefs(file), false);
if (NS_SUCCEEDED(rv)) { -#if !defined(XP_UNIX) || defined(XP_MACOSX) - rv = file->AppendNative("Profiles"_ns); -#endif // We must create the profile directory here if it does not exist. nsresult tmp = EnsureDirectoryExists(file); if (NS_FAILED(tmp)) { @@ -273,9 +270,6 @@ nsresult nsXREDirProvider::GetUserProfilesLocalDir(nsIFile** aResult) { nsresult rv = GetUserDataDirectory(getter_AddRefs(file), true);
if (NS_SUCCEEDED(rv)) { -#if !defined(XP_UNIX) || defined(XP_MACOSX) - rv = file->AppendNative("Profiles"_ns); -#endif // We must create the profile directory here if it does not exist. nsresult tmp = EnsureDirectoryExists(file); if (NS_FAILED(tmp)) { @@ -1371,6 +1365,7 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal) { // Copied from nsAppFileLocationProvider (more or less) nsresult rv; + NS_ENSURE_ARG_POINTER(aFile); nsCOMPtr<nsIFile> localDir;
if (aLocal && gDataDirHomeLocal) { @@ -1380,7 +1375,21 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, return gDataDirHome->Clone(aFile); }
-#if defined(XP_MACOSX) +#if defined(RELATIVE_PROFILE_DIRECTORY) + RefPtr<nsXREDirProvider> singleton = GetSingleton(); + if (!singleton) { + return NS_ERROR_OUT_OF_MEMORY; + } + rv = singleton->GetAppRootDir(getter_AddRefs(localDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString profileDir(RELATIVE_PROFILE_DIRECTORY); + rv = localDir->SetRelativePath(localDir.get(), profileDir); + NS_ENSURE_SUCCESS(rv, rv); + if (aLocal) { + rv = localDir->AppendNative("Caches"_ns); + NS_ENSURE_SUCCESS(rv, rv); + } +#elif defined(XP_MACOSX) FSRef fsRef; OSType folderType; if (aLocal) { @@ -1554,6 +1563,25 @@ nsresult nsXREDirProvider::GetUserDataDirectory(nsIFile** aFile, bool aLocal) { return NS_OK; }
+nsresult nsXREDirProvider::GetAppRootDir(nsIFile** aFile) { + bool persistent = false; + nsCOMPtr<nsIFile> file, appRootDir; + nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &persistent, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + rv = file->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + int levelsToRemove = 1; +#if defined(XP_MACOSX) + levelsToRemove += 2; +#endif + while (levelsToRemove-- > 0) { + rv = file->GetParent(getter_AddRefs(appRootDir)); + NS_ENSURE_SUCCESS(rv, rv); + file = appRootDir; + } + return appRootDir->Clone(aFile); +} + nsresult nsXREDirProvider::EnsureDirectoryExists(nsIFile* aDirectory) { nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700);
@@ -1636,13 +1664,8 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) { }
nsAutoCString profile; - nsAutoCString appName; - nsAutoCString vendor; if (gAppData->profile) { profile = gAppData->profile; - } else { - appName = gAppData->name; - vendor = gAppData->vendor; }
nsresult rv = NS_OK; @@ -1650,23 +1673,12 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) { #if defined(XP_MACOSX) if (!profile.IsEmpty()) { rv = AppendProfileString(aFile, profile.get()); - } else { - // Note that MacOS ignores the vendor when creating the profile hierarchy - - // all application preferences directories live alongside one another in - // ~/Library/Application Support/ - rv = aFile->AppendNative(appName); } NS_ENSURE_SUCCESS(rv, rv);
#elif defined(XP_WIN) if (!profile.IsEmpty()) { rv = AppendProfileString(aFile, profile.get()); - } else { - if (!vendor.IsEmpty()) { - rv = aFile->AppendNative(vendor); - NS_ENSURE_SUCCESS(rv, rv); - } - rv = aFile->AppendNative(appName); } NS_ENSURE_SUCCESS(rv, rv);
@@ -1678,11 +1690,6 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) { rv = aFile->AppendNative(nsDependentCString("mozilla")); NS_ENSURE_SUCCESS(rv, rv); #elif defined(XP_UNIX) - nsAutoCString folder; - // Make it hidden (by starting with "."), except when local (the - // profile is already under ~/.cache or XDG_CACHE_HOME). - if (!aLocal) folder.Assign('.'); - if (!profile.IsEmpty()) { // Skip any leading path characters const char* profileStart = profile.get(); @@ -1690,30 +1697,14 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
// On the off chance that someone wanted their folder to be hidden don't // let it become ".." - if (*profileStart == '.' && !aLocal) profileStart++; + if (*profileStart == '.') profileStart++;
+ // Make it hidden (by starting with "."). + nsAutoCString folder("."); folder.Append(profileStart); ToLowerCase(folder);
rv = AppendProfileString(aFile, folder.BeginReading()); - } else { - if (!vendor.IsEmpty()) { - folder.Append(vendor); - ToLowerCase(folder); - - rv = aFile->AppendNative(folder); - NS_ENSURE_SUCCESS(rv, rv); - - folder.Truncate(); - } - - // This can be the case in tests. - if (!appName.IsEmpty()) { - folder.Append(appName); - ToLowerCase(folder); - - rv = aFile->AppendNative(folder); - } } NS_ENSURE_SUCCESS(rv, rv);
diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h index e28a4fef5bc63..a76b087d785a3 100644 --- a/toolkit/xre/nsXREDirProvider.h +++ b/toolkit/xre/nsXREDirProvider.h @@ -109,6 +109,14 @@ class nsXREDirProvider final : public nsIDirectoryServiceProvider2, */ nsresult GetProfileDir(nsIFile** aResult);
+ /** + * Get the path to the base application directory. + * + * In almost all platforms it is the directory that contains the Firefox + * executable; on macOS we remove also Contents/MacOS from it. + */ + nsresult GetAppRootDir(nsIFile** aFile); + protected: nsresult GetFilesInternal(const char* aProperty, nsISimpleEnumerator** aResult); diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp index ef974f99048f8..60b1775eda971 100644 --- a/xpcom/io/nsAppFileLocationProvider.cpp +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -243,11 +243,43 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, return NS_ERROR_INVALID_ARG; }
- nsresult rv; + nsresult rv = NS_ERROR_UNEXPECTED; bool exists; nsCOMPtr<nsIFile> localDir;
-#if defined(MOZ_WIDGET_COCOA) +#if defined(RELATIVE_PROFILE_DIRECTORY) + nsCOMPtr<nsIProperties> directoryService( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool persistent = false; + nsCOMPtr<nsIFile> file, appRootDir; + rv = directoryService->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + rv = file->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + int levelsToRemove = 1; +#if defined(XP_MACOSX) + levelsToRemove += 2; +#endif + while (levelsToRemove-- > 0) { + rv = file->GetParent(getter_AddRefs(appRootDir)); + NS_ENSURE_SUCCESS(rv, rv); + file = appRootDir; + } + + localDir = appRootDir; + nsAutoCString profileDir(RELATIVE_PROFILE_DIRECTORY); + rv = localDir->SetRelativePath(localDir.get(), profileDir); + NS_ENSURE_SUCCESS(rv, rv); + + if (aLocal) { + rv = localDir->AppendNative("Caches"_ns); + NS_ENSURE_SUCCESS(rv, rv); + } + +#elif defined(MOZ_WIDGET_COCOA) FSRef fsRef; OSType folderType = aLocal ? (OSType)kCachedDataFolderType : (OSType)kDomainLibraryFolderType; @@ -286,10 +318,6 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, # error dont_know_how_to_get_product_dir_on_your_platform #endif
- rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR); - if (NS_FAILED(rv)) { - return rv; - } rv = localDir->Exists(&exists);
if (NS_SUCCEEDED(rv) && !exists) { @@ -308,10 +336,6 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, //---------------------------------------------------------------------------------------- // GetDefaultUserProfileRoot - Gets the directory which contains each user // profile dir -// -// UNIX : ~/.mozilla/ -// WIN : <Application Data folder on user's machine>\Mozilla\Profiles -// Mac : :Documents:Mozilla:Profiles: //---------------------------------------------------------------------------------------- nsresult nsAppFileLocationProvider::GetDefaultUserProfileRoot( nsIFile** aLocalFile, bool aLocal) { @@ -327,23 +351,6 @@ nsresult nsAppFileLocationProvider::GetDefaultUserProfileRoot( return rv; }
-#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN) - // These 3 platforms share this part of the path - do them as one - rv = localDir->AppendRelativeNativePath("Profiles"_ns); - if (NS_FAILED(rv)) { - return rv; - } - - bool exists; - rv = localDir->Exists(&exists); - if (NS_SUCCEEDED(rv) && !exists) { - rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0775); - } - if (NS_FAILED(rv)) { - return rv; - } -#endif - localDir.forget(aLocalFile);
return rv;
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 6a622ebd6cd39b1d3f8a8f37a204b2269851f8d8 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Thu Apr 21 10:40:26 2016 -0400
Bug 18800: Remove localhost DNS lookup in nsProfileLock.cpp
Instead of using the local computer's IP address within symlink-based profile lock signatures, always use 127.0.0.1.
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1769028 --- toolkit/profile/nsProfileLock.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/toolkit/profile/nsProfileLock.cpp b/toolkit/profile/nsProfileLock.cpp index 28d38c11684e3..d54f2eb0ff5e6 100644 --- a/toolkit/profile/nsProfileLock.cpp +++ b/toolkit/profile/nsProfileLock.cpp @@ -307,6 +307,16 @@ nsresult nsProfileLock::LockWithSymlink(nsIFile* aLockFile, struct in_addr inaddr; inaddr.s_addr = htonl(INADDR_LOOPBACK);
+ // We still have not loaded the profile, so we may not have proxy information. + // Avoiding a DNS lookup in this stage makes sure any proxy is not bypassed. + // By default, the lookup is enabled, but when it is not, we use 127.0.0.1 + // for the IP address portion of the lock signature. + // However, this may cause the browser to refuse to start in the rare case + // that all of the following conditions are met: + // 1. The browser profile is on a network file system. + // 2. The file system does not support fcntl() locking. + // 3. The browser is run from two different computers at the same time. +#ifndef MOZ_PROXY_BYPASS_PROTECTION char hostname[256]; PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname); if (status == PR_SUCCESS) { @@ -315,6 +325,7 @@ nsresult nsProfileLock::LockWithSymlink(nsIFile* aLockFile, status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent); if (status == PR_SUCCESS) memcpy(&inaddr, hostent.h_addr, sizeof inaddr); } +#endif
mozilla::SmprintfPointer signature = mozilla::Smprintf("%s:%s%lu", inet_ntoa(inaddr),
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit dd9c91a922883a8557a0368a9c4290b33ae557f1 Author: Alex Catarineu acat@torproject.org AuthorDate: Wed Oct 30 10:44:48 2019 +0100
Bug 27604: Fix addon issues when moving the profile directory
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1429838 --- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 5bd7cabb0f73a..c1cef2814b386 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -475,7 +475,7 @@ class XPIState {
// Builds prior to be 1512436 did not include the rootURI property. // If we're updating from such a build, add that property now. - if (!("rootURI" in this) && this.file) { + if (this.file) { this.rootURI = getURIForResourceInFile(this.file, "").spec; }
@@ -488,7 +488,10 @@ class XPIState { saved.currentModifiedTime != this.lastModifiedTime ) { this.lastModifiedTime = saved.currentModifiedTime; - } else if (saved.currentModifiedTime === null) { + } else if ( + saved.currentModifiedTime === null && + (!this.file || !this.file.exists()) + ) { this.missing = true; } } @@ -1449,6 +1452,7 @@ var XPIStates = {
if (shouldRestoreLocationData && oldState[loc.name]) { loc.restore(oldState[loc.name]); + changed = changed || loc.path != oldState[loc.name].path; } changed = changed || loc.changed;
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit ee630bac7d57b87a7913ac5f3351a3f9cb2b3249 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Thu Apr 16 17:07:09 2020 -0400
Bug 32418: Add a configure flag to load policies only from the local policies.json
Add a configuration flag to make Enterprise Policies mechanism only consult a policies.json file (avoiding the Windows Registry, macOS's file system attributes, and /etc/firefox/policies/policies.json on other OS).
We avoid system policies because their proxy settings override our preferences, however updates can be disabled only with enterprise policies, so we allow them from a local file as a trade off. --- browser/config/mozconfigs/base-browser | 1 + .../enterprisepolicies/EnterprisePoliciesParent.jsm | 13 +++++++++++-- toolkit/components/enterprisepolicies/moz.build | 3 +++ toolkit/moz.configure | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/browser/config/mozconfigs/base-browser b/browser/config/mozconfigs/base-browser index 9963ab7ef9e62..cdc493f473022 100644 --- a/browser/config/mozconfigs/base-browser +++ b/browser/config/mozconfigs/base-browser @@ -26,6 +26,7 @@ ac_add_options --disable-parental-controls # Let's make sure no preference is enabling either Adobe's or Google's CDM. ac_add_options --disable-eme ac_add_options --enable-proxy-bypass-protection +ac_add_options --disable-system-policies
# Disable telemetry ac_add_options MOZ_TELEMETRY_REPORTING= diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm index f5de14798de15..8022d65ab0b2d 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm @@ -4,6 +4,9 @@
var EXPORTED_SYMBOLS = ["EnterprisePoliciesManager"];
+// If MOZ_AVOID_SYSTEM_POLICIES is defined, policies will be looked for only +// in ${InstallDir}/distribution + const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); @@ -13,9 +16,11 @@ const { AppConstants } = ChromeUtils.import( );
XPCOMUtils.defineLazyModuleGetters(this, { +#ifndef MOZ_AVOID_SYSTEM_POLICIES WindowsGPOParser: "resource://gre/modules/policies/WindowsGPOParser.jsm", macOSPoliciesParser: "resource://gre/modules/policies/macOSPoliciesParser.jsm", +#endif Policies: "resource:///modules/policies/Policies.jsm", JsonSchemaValidator: "resource://gre/modules/components-utils/JsonSchemaValidator.jsm", @@ -140,11 +145,13 @@ EnterprisePoliciesManager.prototype = {
_chooseProvider() { let platformProvider = null; +#ifndef MOZ_AVOID_SYSTEM_POLICIES if (AppConstants.platform == "win") { platformProvider = new WindowsGPOPoliciesProvider(); } else if (AppConstants.platform == "macosx") { platformProvider = new macOSPoliciesProvider(); } +#endif let jsonProvider = new JSONPoliciesProvider(); if (platformProvider && platformProvider.hasPolicies) { if (jsonProvider.hasPolicies) { @@ -491,7 +498,7 @@ class JSONPoliciesProvider {
_getConfigurationFile() { let configFile = null; - +#ifndef MOZ_AVOID_SYSTEM_POLICIES if (AppConstants.platform == "linux") { let systemConfigFile = Cc["@mozilla.org/file/local;1"].createInstance( Ci.nsIFile @@ -504,7 +511,7 @@ class JSONPoliciesProvider { return systemConfigFile; } } - +#endif try { let perUserPath = Services.prefs.getBoolPref(PREF_PER_USER_DIR, false); if (perUserPath) { @@ -585,6 +592,7 @@ class JSONPoliciesProvider { } }
+#ifndef MOZ_AVOID_SYSTEM_POLICIES class WindowsGPOPoliciesProvider { constructor() { this._policies = null; @@ -686,3 +694,4 @@ class CombinedProvider { return false; } } +#endif diff --git a/toolkit/components/enterprisepolicies/moz.build b/toolkit/components/enterprisepolicies/moz.build index 09d2046e1bd79..3f685d3fbbd66 100644 --- a/toolkit/components/enterprisepolicies/moz.build +++ b/toolkit/components/enterprisepolicies/moz.build @@ -19,6 +19,9 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": EXTRA_JS_MODULES += [ "EnterprisePolicies.jsm", "EnterprisePoliciesContent.jsm", + ] + + EXTRA_PP_JS_MODULES += [ "EnterprisePoliciesParent.jsm", ]
diff --git a/toolkit/moz.configure b/toolkit/moz.configure index 026e7fa7ba512..ffda1f21d7d64 100644 --- a/toolkit/moz.configure +++ b/toolkit/moz.configure @@ -2530,3 +2530,21 @@ def oxidized_breakpad(target):
set_config("MOZ_OXIDIZED_BREAKPAD", True, when=oxidized_breakpad) set_define("MOZ_OXIDIZED_BREAKPAD", True, when=oxidized_breakpad) + +# Avoid system policies +# ============================================================== + +option( + "--disable-system-policies", + help="Prevent reading policies from Windows registry, macOS's file system attributes, and /etc/firefox", +) + + +@depends("--disable-system-policies") +def system_policies(value): + if not value: + return True + + +set_config("MOZ_AVOID_SYSTEM_POLICIES", system_policies) +set_define("MOZ_AVOID_SYSTEM_POLICIES", system_policies)
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit f4639ba025e48820fc49ce17f22024b38fa3533e Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Mon Sep 29 14:30:19 2014 -0700
Bug 13028: Prevent potential proxy bypass cases.
It looks like these cases should only be invoked in the NSS command line tools, and not the browser, but I decided to patch them anyway because there literally is a maze of network function pointers being passed around, and it's very hard to tell if some random code might not pass in the proper proxied versions of the networking code here by accident.
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1433509 --- security/nss/lib/certhigh/ocsp.c | 4 ++++ security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_socket.c | 12 ++++++++++++ 2 files changed, 16 insertions(+)
diff --git a/security/nss/lib/certhigh/ocsp.c b/security/nss/lib/certhigh/ocsp.c index cea8456606bf1..76622614a80a2 100644 --- a/security/nss/lib/certhigh/ocsp.c +++ b/security/nss/lib/certhigh/ocsp.c @@ -2927,6 +2927,9 @@ loser: static PRFileDesc * ocsp_ConnectToHost(const char *host, PRUint16 port) { +#ifdef MOZ_PROXY_BYPASS_PROTECTION + return NULL; +#else PRFileDesc *sock = NULL; PRIntervalTime timeout; PRNetAddr addr; @@ -2985,6 +2988,7 @@ loser: if (netdbbuf != NULL) PORT_Free(netdbbuf); return NULL; +#endif }
/* diff --git a/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_socket.c b/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_socket.c index e8698376b5bec..f34e102721d2b 100644 --- a/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_socket.c +++ b/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_socket.c @@ -1322,6 +1322,9 @@ pkix_pl_Socket_Create( PKIX_PL_Socket **pSocket, void *plContext) { +#ifdef MOZ_PROXY_BYPASS_PROTECTION + PKIX_ERROR(PKIX_PRNEWTCPSOCKETFAILED); +#else PKIX_PL_Socket *socket = NULL;
PKIX_ENTER(SOCKET, "pkix_pl_Socket_Create"); @@ -1369,6 +1372,7 @@ cleanup: }
PKIX_RETURN(SOCKET); +#endif }
/* @@ -1418,6 +1422,9 @@ pkix_pl_Socket_CreateByName( PKIX_PL_Socket **pSocket, void *plContext) { +#ifdef MOZ_PROXY_BYPASS_PROTECTION + PKIX_ERROR(PKIX_PRNEWTCPSOCKETFAILED); +#else PRNetAddr netAddr; PKIX_PL_Socket *socket = NULL; char *sepPtr = NULL; @@ -1520,6 +1527,7 @@ cleanup: }
PKIX_RETURN(SOCKET); +#endif }
/* @@ -1571,6 +1579,9 @@ pkix_pl_Socket_CreateByHostAndPort( PKIX_PL_Socket **pSocket, void *plContext) { +#ifdef MOZ_PROXY_BYPASS_PROTECTION + PKIX_ERROR(PKIX_PRNEWTCPSOCKETFAILED); +#else PRNetAddr netAddr; PKIX_PL_Socket *socket = NULL; char *sepPtr = NULL; @@ -1658,6 +1669,7 @@ cleanup: }
PKIX_RETURN(SOCKET); +#endif }
/*
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 970e7f414191ecef93e027ac3471f659ae04a1e6 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue Apr 29 13:08:24 2014 -0400
Bug 11641: Disable remoting by default.
Unless the -osint command line flag is used, the browser now defaults to the equivalent of -no-remote. There is a new -allow-remote flag that may be used to restore the original (Firefox-like) default behavior. --- toolkit/xre/nsAppRunner.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 0d4826fa0b37d..acc92392cf239 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -1837,8 +1837,10 @@ static void DumpHelp() { " --migration Start with migration wizard.\n" " --ProfileManager Start with ProfileManager.\n" #ifdef MOZ_HAS_REMOTE - " --no-remote Do not accept or send remote commands; implies\n" + " --no-remote (default) Do not accept or send remote commands; " + "implies\n" " --new-instance.\n" + " --allow-remote Accept and send remote commands.\n" " --new-instance Open new instance, not a new window in running " "instance.\n" #endif @@ -3960,16 +3962,25 @@ int XREMain::XRE_mainInit(bool* aExitFlag) { gSafeMode);
#if defined(MOZ_HAS_REMOTE) + // We disable remoting by default, unless -osint is used. + bool allowRemote = (CheckArg("allow-remote") == ARG_FOUND); + bool isOsint = (CheckArg("osint", nullptr, CheckArgFlag::None) == ARG_FOUND); + if (!allowRemote && !isOsint) { + SaveToEnv("MOZ_NO_REMOTE=1"); + } // Handle --no-remote and --new-instance command line arguments. Setup // the environment to better accommodate other components and various // restart scenarios. ar = CheckArg("no-remote"); - if (ar == ARG_FOUND || EnvHasValue("MOZ_NO_REMOTE")) { + if ((ar == ARG_FOUND) && allowRemote) { + PR_fprintf(PR_STDERR, + "Error: argument --no-remote is invalid when argument " + "--allow-remote is specified\n"); + return 1; + } + if (EnvHasValue("MOZ_NO_REMOTE")) { mDisableRemoteClient = true; mDisableRemoteServer = true; - if (!EnvHasValue("MOZ_NO_REMOTE")) { - SaveToEnv("MOZ_NO_REMOTE=1"); - } }
ar = CheckArg("new-instance");
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 19e8ff3fe5256884815313599ef8077e35e1e546 Author: Georg Koppen gk@torproject.org AuthorDate: Fri Aug 4 05:55:49 2017 +0000
Bug 21830: Copying large text from web console leaks to /tmp
Patch written by Neill Miller
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1768907 --- widget/nsTransferable.cpp | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/widget/nsTransferable.cpp b/widget/nsTransferable.cpp index c82549a4d1d14..f8ecfbff0983a 100644 --- a/widget/nsTransferable.cpp +++ b/widget/nsTransferable.cpp @@ -33,6 +33,7 @@ Notes to self: #include "nsILoadContext.h" #include "nsXULAppAPI.h" #include "mozilla/UniquePtr.h" +#include "mozilla/Preferences.h"
using namespace mozilla;
@@ -195,6 +196,11 @@ nsTransferable::Init(nsILoadContext* aContext) {
if (aContext) { mPrivateData = aContext->UsePrivateBrowsing(); + } else { + // without aContext here to provide PrivateBrowsing information, + // we defer to the active configured setting + mPrivateData = + mozilla::Preferences::GetBool("browser.privatebrowsing.autostart"); } #ifdef DEBUG mInitialized = true;
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit aa4581a0fa54c703ac4f344da2a58b78f963cf19 Author: Igor Oliveira igor.oliveira@posteo.net AuthorDate: Sun Dec 10 18:16:59 2017 -0200
Bug 23104: Add a default line height compensation
Many fonts have issues with their vertical metrics. they are used to influence the height of ascenders and depth of descenders. Gecko uses it to calculate the line height (font height + ascender + descender), however because of that idiosyncratic behavior across multiple operating systems, it can be used to identify the user's OS.
The solution proposed in the patch uses a default factor to be multiplied with the font size, simulating the concept of ascender and descender. This way all operating systems will have the same line height only and only if the frame is outside the chrome. --- layout/generic/ReflowInput.cpp | 19 +++++++++--- layout/generic/test/mochitest.ini | 1 + layout/generic/test/test_tor_bug23104.html | 50 ++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-)
diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp index 2c56afd2e02a0..4d30c7762c14b 100644 --- a/layout/generic/ReflowInput.cpp +++ b/layout/generic/ReflowInput.cpp @@ -31,6 +31,7 @@ #include "mozilla/SVGUtils.h" #include "mozilla/dom/HTMLInputElement.h" #include "nsGridContainerFrame.h" +#include "nsContentUtils.h"
using namespace mozilla; using namespace mozilla::css; @@ -2642,7 +2643,8 @@ void ReflowInput::CalculateBlockSideMargins() {
// For risk management, we use preference to control the behavior, and // eNoExternalLeading is the old behavior. -static nscoord GetNormalLineHeight(nsFontMetrics* aFontMetrics) { +static nscoord GetNormalLineHeight(nsIContent* aContent, + nsFontMetrics* aFontMetrics) { MOZ_ASSERT(nullptr != aFontMetrics, "no font metrics");
nscoord normalLineHeight; @@ -2650,6 +2652,12 @@ static nscoord GetNormalLineHeight(nsFontMetrics* aFontMetrics) { nscoord externalLeading = aFontMetrics->ExternalLeading(); nscoord internalLeading = aFontMetrics->InternalLeading(); nscoord emHeight = aFontMetrics->EmHeight(); + + if (nsContentUtils::ShouldResistFingerprinting() && + !aContent->IsInChromeDocument()) { + return NSToCoordRound(emHeight * NORMAL_LINE_HEIGHT_FACTOR); + } + switch (GetNormalLineHeightCalcControl()) { case eIncludeExternalLeading: normalLineHeight = emHeight + internalLeading + externalLeading; @@ -2667,7 +2675,8 @@ static nscoord GetNormalLineHeight(nsFontMetrics* aFontMetrics) { return normalLineHeight; }
-static inline nscoord ComputeLineHeight(ComputedStyle* aComputedStyle, +static inline nscoord ComputeLineHeight(nsIContent* aContent, + ComputedStyle* aComputedStyle, nsPresContext* aPresContext, nscoord aBlockBSize, float aFontSizeInflation) { @@ -2696,7 +2705,7 @@ static inline nscoord ComputeLineHeight(ComputedStyle* aComputedStyle,
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle( aComputedStyle, aPresContext, aFontSizeInflation); - return GetNormalLineHeight(fm); + return GetNormalLineHeight(aContent, fm); }
nscoord ReflowInput::CalcLineHeight() const { @@ -2718,7 +2727,7 @@ nscoord ReflowInput::CalcLineHeight(nsIContent* aContent, float aFontSizeInflation) { MOZ_ASSERT(aComputedStyle, "Must have a ComputedStyle");
- nscoord lineHeight = ComputeLineHeight(aComputedStyle, aPresContext, + nscoord lineHeight = ComputeLineHeight(aContent, aComputedStyle, aPresContext, aBlockBSize, aFontSizeInflation);
NS_ASSERTION(lineHeight >= 0, "ComputeLineHeight screwed up"); @@ -2731,7 +2740,7 @@ nscoord ReflowInput::CalcLineHeight(nsIContent* aContent, if (!lh.IsNormal()) { RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle( aComputedStyle, aPresContext, aFontSizeInflation); - nscoord normal = GetNormalLineHeight(fm); + nscoord normal = GetNormalLineHeight(aContent, fm); if (lineHeight < normal) { lineHeight = normal; } diff --git a/layout/generic/test/mochitest.ini b/layout/generic/test/mochitest.ini index bde689457ebcf..af9dbe3c04448 100644 --- a/layout/generic/test/mochitest.ini +++ b/layout/generic/test/mochitest.ini @@ -145,3 +145,4 @@ skip-if = debug == true || tsan # the test is slow. tsan: bug 1612707 support-files = file_reframe_for_lazy_load_image.html [test_bug1655135.html] +[test_tor_bug23104.html] diff --git a/layout/generic/test/test_tor_bug23104.html b/layout/generic/test/test_tor_bug23104.html new file mode 100644 index 0000000000000..8ff1d2190c45f --- /dev/null +++ b/layout/generic/test/test_tor_bug23104.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<meta charset="UTF-8"> +<html> +<head> + <title>Test for Tor Bug #23104: CSS line-height reveals the platform Tor browser is running</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <style type="text/css"> + span { + background-color: #000; + color: #fff; + font-size: 16.5px; + } + </style> +</head> +<body> +<span id="test1">Test1</span> +<span id="test2">كلمة</span> +<span id="test3">ação</span> +<script> + +let setPref = async function (key, value) { + await SpecialPowers.pushPrefEnv({"set": [[key, value]]}); +} + +function getStyle(el, styleprop) { + el = document.getElementById(el); + return document.defaultView.getComputedStyle(el, null).getPropertyValue(styleprop); +} + +function validateElement(elementName, isFingerprintResistent) { + var fontSize = getStyle(elementName, 'font-size'); + var lineHeight = getStyle(elementName, 'line-height'); + var validationCb = isFingerprintResistent ? is : isnot; + validationCb(parseFloat(lineHeight), Math.round(parseFloat(fontSize)) * 1.2, 'Line Height validation'); +} + +add_task(async function() { + await setPref("layout.css.line-height.normal-as-resolved-value.enabled", false); + for (let resistFingerprintingValue of [true, false]) { + await setPref("privacy.resistFingerprinting", resistFingerprintingValue); + for (let elementId of ['test1', 'test2', 'test3']) { + validateElement(elementId, resistFingerprintingValue); + } + } +}); + +</script> +</body> +</html>
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 3f28b38a3ffb6f360b503523c9127fd989f70c9d Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Wed May 18 19:22:37 2022 +0200
Bug 40309: Avoid using regional OS locales
Avoid regional OS locales if the pref `intl.regional_prefs.use_os_locales` is false but RFP is enabled. --- intl/locale/LocaleService.cpp | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/intl/locale/LocaleService.cpp b/intl/locale/LocaleService.cpp index 022d41cab2e2d..accf0ef75d168 100644 --- a/intl/locale/LocaleService.cpp +++ b/intl/locale/LocaleService.cpp @@ -13,6 +13,7 @@ #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/intl/MozLocale.h" #include "mozilla/intl/OSPreferences.h" +#include "nsContentUtils.h" #include "nsDirectoryService.h" #include "nsDirectoryServiceDefs.h" #include "nsIObserverService.h" @@ -442,6 +443,11 @@ LocaleService::GetAppLocaleAsBCP47(nsACString& aRetVal) {
NS_IMETHODIMP LocaleService::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) { + if (nsContentUtils::ShouldResistFingerprinting()) { + GetAppLocalesAsBCP47(aRetVal); + return NS_OK; + } + bool useOSLocales = Preferences::GetBool("intl.regional_prefs.use_os_locales", false);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 7794e1b067b70ced5dfdfe856027dfd499eccb69 Author: Matthew Finkel sysrqb@torproject.org AuthorDate: Mon May 17 18:09:09 2021 +0000
Bug 40432: Prevent probing installed applications
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1711084 --- .../exthandler/nsExternalHelperAppService.cpp | 30 ++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index bcfaab121a250..122524c8ce7a0 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -938,8 +938,33 @@ nsresult nsExternalHelperAppService::GetFileTokenForPath( ////////////////////////////////////////////////////////////////////////////////////////////////////// // begin external protocol service default implementation... ////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const char kExternalProtocolPrefPrefix[] = + "network.protocol-handler.external."; +static const char kExternalProtocolDefaultPref[] = + "network.protocol-handler.external-default"; + NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists( const char* aProtocolScheme, bool* aHandlerExists) { + + // Replicate the same check performed in LoadURI. + // Deny load if the prefs say to do so + nsAutoCString externalPref(kExternalProtocolPrefPrefix); + externalPref += aProtocolScheme; + bool allowLoad = false; + *aHandlerExists = false; + if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) { + // no scheme-specific value, check the default + if (NS_FAILED( + Preferences::GetBool(kExternalProtocolDefaultPref, &allowLoad))) { + return NS_OK; // missing default pref + } + } + + if (!allowLoad) { + return NS_OK; // explicitly denied + } + nsCOMPtr<nsIHandlerInfo> handlerInfo; nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme), getter_AddRefs(handlerInfo)); @@ -982,11 +1007,6 @@ NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol( return NS_OK; }
-static const char kExternalProtocolPrefPrefix[] = - "network.protocol-handler.external."; -static const char kExternalProtocolDefaultPref[] = - "network.protocol-handler.external-default"; - // static nsresult nsExternalHelperAppService::EscapeURI(nsIURI* aURI, nsIURI** aResult) { MOZ_ASSERT(aURI);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 918405506e829010b5f5ab76e5cc5462200c6158 Author: Richard Pospesel richard@torproject.org AuthorDate: Mon Oct 28 17:42:17 2019 -0700
Bug 32220: Improve the letterboxing experience
CSS and JS changes to alter the UX surrounding letterboxing. The browser element containing page content is now anchored to the bottom of the toolbar, and the remaining letterbox margin is the same color as the firefox chrome. The letterbox margin and border are tied to the currently selected theme.
Also adds a 'needsLetterbox' property to tabbrowser.xml to fix a race condition present when using the 'isEmpty' property. Using 'isEmpty' as a proxy for 'needsLetterbox' resulted in over-zealous/unnecessary letterboxing of about:blank tabs.
Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1594455 --- browser/base/content/browser.css | 7 ++ browser/base/content/tabbrowser-tab.js | 9 +++ browser/themes/shared/tabs.inc.css | 6 ++ .../components/resistfingerprinting/RFPHelper.jsm | 94 +++++++++++++++++++--- 4 files changed, 104 insertions(+), 12 deletions(-)
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 2d74162b15435..0a766b976fc5e 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -94,6 +94,13 @@ body { } }
+.browserStack > browser.letterboxing { + border-color: var(--chrome-content-separator-color); + border-style: solid; + border-width : 1px; + border-top: none; +} + %ifdef MENUBAR_CAN_AUTOHIDE #toolbar-menubar[autohide="true"] { overflow: hidden; diff --git a/browser/base/content/tabbrowser-tab.js b/browser/base/content/tabbrowser-tab.js index 2d12c357da146..524493ac9afbe 100644 --- a/browser/base/content/tabbrowser-tab.js +++ b/browser/base/content/tabbrowser-tab.js @@ -230,6 +230,15 @@ return true; }
+ get needsLetterbox() { + let browser = this.linkedBrowser; + if (isBlankPageURL(browser.currentURI.spec)) { + return false; + } + + return true; + } + get lastAccessed() { return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed; } diff --git a/browser/themes/shared/tabs.inc.css b/browser/themes/shared/tabs.inc.css index 2a572d2dce2cf..98f3c68e4a042 100644 --- a/browser/themes/shared/tabs.inc.css +++ b/browser/themes/shared/tabs.inc.css @@ -50,6 +50,12 @@ background-color: var(--tabpanel-background-color); }
+/* extend down the toolbar's colors when letterboxing is enabled*/ +#tabbrowser-tabpanels.letterboxing { + background-color: var(--toolbar-bgcolor); + background-image: var(--toolbar-bgimage); +} + #tabbrowser-tabs, #tabbrowser-arrowscrollbox, #tabbrowser-tabs[positionpinnedtabs] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[pinned] { diff --git a/toolkit/components/resistfingerprinting/RFPHelper.jsm b/toolkit/components/resistfingerprinting/RFPHelper.jsm index 166ad21e9013c..9520d87206319 100644 --- a/toolkit/components/resistfingerprinting/RFPHelper.jsm +++ b/toolkit/components/resistfingerprinting/RFPHelper.jsm @@ -40,6 +40,7 @@ class _RFPHelper { // ============================================================================ constructor() { this._initialized = false; + this._borderDimensions = null; }
init() { @@ -361,6 +362,24 @@ class _RFPHelper { }); }
+ getBorderDimensions(aBrowser) { + if (this._borderDimensions) { + return this._borderDimensions; + } + + const win = aBrowser.ownerGlobal; + const browserStyle = win.getComputedStyle(aBrowser); + + this._borderDimensions = { + top : parseInt(browserStyle.borderTopWidth), + right: parseInt(browserStyle.borderRightWidth), + bottom : parseInt(browserStyle.borderBottomWidth), + left : parseInt(browserStyle.borderLeftWidth), + }; + + return this._borderDimensions; + } + _addOrClearContentMargin(aBrowser) { let tab = aBrowser.getTabBrowser().getTabForBrowser(aBrowser);
@@ -369,9 +388,13 @@ class _RFPHelper { return; }
+ // we add the letterboxing class even if the content does not need letterboxing + // in which case margins are set such that the borders are hidden + aBrowser.classList.add("letterboxing"); + // We should apply no margin around an empty tab or a tab with system // principal. - if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) { + if (!tab.needsLetterbox || aBrowser.contentPrincipal.isSystemPrincipal) { this._clearContentViewMargin(aBrowser); } else { this._roundContentView(aBrowser); @@ -539,10 +562,29 @@ class _RFPHelper { // Calculating the margins around the browser element in order to round the // content viewport. We will use a 200x100 stepping if the dimension set // is not given. - let margins = calcMargins(containerWidth, containerHeight); + + const borderDimensions = this.getBorderDimensions(aBrowser); + const marginDims = calcMargins(containerWidth, containerHeight - borderDimensions.top); + + let margins = { + top : 0, + right : 0, + bottom : 0, + left : 0, + }; + + // snap browser element to top + margins.top = 0; + // and leave 'double' margin at the bottom + margins.bottom = 2 * marginDims.height - borderDimensions.bottom; + // identical margins left and right + margins.right = marginDims.width - borderDimensions.right; + margins.left = marginDims.width - borderDimensions.left; + + const marginStyleString = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`;
// If the size of the content is already quantized, we do nothing. - if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) { + if (aBrowser.style.margin === marginStyleString) { log("_roundContentView[" + logId + "] is_rounded == true"); if (this._isLetterboxingTesting) { log( @@ -563,19 +605,35 @@ class _RFPHelper { "_roundContentView[" + logId + "] setting margins to " + - margins.width + - " x " + - margins.height + marginStyleString ); - // One cannot (easily) control the color of a margin unfortunately. - // An initial attempt to use a border instead of a margin resulted - // in offset event dispatching; so for now we use a colorless margin. - aBrowser.style.margin = `${margins.height}px ${margins.width}px`; + + // The margin background color is determined by the background color of the + // window's tabpanels#tabbrowser-tabpanels element + aBrowser.style.margin = marginStyleString; }); }
_clearContentViewMargin(aBrowser) { + const borderDimensions = this.getBorderDimensions(aBrowser); + // set the margins such that the browser elements border is visible up top, but + // are rendered off-screen on the remaining sides + let margins = { + top : 0, + right : -borderDimensions.right, + bottom : -borderDimensions.bottom, + left : -borderDimensions.left, + }; + const marginStyleString = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`; + + aBrowser.ownerGlobal.requestAnimationFrame(() => { + aBrowser.style.margin = marginStyleString; + }); + } + + _removeLetterboxing(aBrowser) { aBrowser.ownerGlobal.requestAnimationFrame(() => { + aBrowser.classList.remove("letterboxing"); aBrowser.style.margin = ""; }); } @@ -593,6 +651,11 @@ class _RFPHelper { aWindow.gBrowser.addTabsProgressListener(this); aWindow.addEventListener("TabOpen", this);
+ const tabPanel = aWindow.document.getElementById("tabbrowser-tabpanels"); + if (tabPanel) { + tabPanel.classList.add("letterboxing"); + } + // Rounding the content viewport. this._updateMarginsForTabsInWindow(aWindow); } @@ -616,10 +679,17 @@ class _RFPHelper { tabBrowser.removeTabsProgressListener(this); aWindow.removeEventListener("TabOpen", this);
- // Clear all margins and tooltip for all browsers. + // revert tabpanel's background colors to default + const tabPanel = aWindow.document.getElementById("tabbrowser-tabpanels"); + if (tabPanel) { + tabPanel.classList.remove("letterboxing"); + } + + // and revert each browser element to default, + // restore default margins and remove letterboxing class for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; - this._clearContentViewMargin(browser); + this._removeLetterboxing(browser); } }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 5e38e274a262b9af99fc4c5bec4e00d3e5526385 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Tue Sep 10 16:21:47 2013 -0700
Bug 2176: Rebrand Firefox to TorBrowser
See also Bugs #5194, #7187, #8115, #8219.
This patch does some basic renaming of Firefox to TorBrowser. The rest of the branding is done by images and icons.
Also fix bug 27905.
Bug 25702: Update Tor Browser icon to follow design guidelines
- Updated all of the branding in /browser/branding/official with new 'stable' icon series. - Updated /extensions/onboarding/content/img/tor-watermark.png with new icon and add the source svg in the same directory - Copied /browser/branding/official over /browser/branding/nightly and the new /browser/branding/alpha directories. Replaced content with 'nightly' and 'alpha' icon series. Updated VisualElements_70.png and VisualElements_150.png with updated icons in each branding directory (fixes #22654) - Updated firefox.VisualElementsManfiest.xml with updated colors in each branding directory - Added firefox.svg to each branding directory from which all the other icons are derived (apart from document.icns and document.ico) - Added default256.png and default512.png icons - Updated aboutTBUpdate.css to point to branding-aware icon128.png and removed original icon - Use the Tor Browser icon within devtools/client/themes/images/.
Bug 30631: Blurry Tor Browser icon on macOS app switcher
It would seem the png2icns tool does not generate correct icns files and so on macOS the larger icons were missing resulting in blurry icons in the OS chrome. Regenerated the padded icons in a macOS VM using iconutil.
Bug 28196: preparations for using torbutton tor-browser-brand.ftl
A small change to Fluent FileSource class is required so that we can register a new source without its supported locales being counted as available locales for the browser.
Bug 31803: Replaced about:debugging logo with flat version
Bug 21724: Make Firefox and Tor Browser distinct macOS apps
When macOS opens a document or selects a default browser, it sometimes uses the CFBundleSignature. Changing from the Firefox MOZB signature to a different signature TORB allows macOS to distinguish between Firefox and Tor Browser.
Bug 32092: Fix Tor Browser Support link in preferences
For bug 40562, we moved onionPattern* from bug 27476 to here, as about:tor needs these files but it is included earlier. --- browser/app/Makefile.in | 2 +- browser/app/macbuild/Contents/Info.plist.in | 2 +- browser/branding/alpha/VisualElements_150.png | Bin 0 -> 8412 bytes browser/branding/alpha/VisualElements_70.png | Bin 0 -> 3496 bytes browser/branding/alpha/background.png | Bin 0 -> 33362 bytes browser/branding/alpha/bgstub.jpg | Bin 0 -> 12506 bytes browser/branding/alpha/bgstub_2x.jpg | Bin 0 -> 49771 bytes browser/branding/alpha/branding.nsi | 64 +++++++++++++++ .../locales/moz.build => alpha/configure.sh} | 6 +- browser/branding/alpha/content/about-logo.png | Bin 0 -> 21173 bytes browser/branding/alpha/content/about-logo.svg | 1 + browser/branding/alpha/content/about-logo@2x.png | Bin 0 -> 51309 bytes browser/branding/alpha/content/about-wordmark.svg | 36 +++++++++ browser/branding/alpha/content/about.png | Bin 0 -> 18520 bytes browser/branding/alpha/content/aboutDialog.css | 49 ++++++++++++ browser/branding/alpha/content/aboutlogins.svg | 59 ++++++++++++++ .../branding/alpha/content/firefox-wordmark.svg | 1 + .../alpha/content/identity-icons-brand.svg | 8 ++ browser/branding/{nightly => alpha}/content/jar.mn | 4 + .../{nightly/locales => alpha/content}/moz.build | 2 - browser/branding/alpha/content/tor-styles.css | 13 +++ browser/branding/alpha/default128.png | Bin 0 -> 9397 bytes browser/branding/alpha/default16.png | Bin 0 -> 811 bytes browser/branding/alpha/default22.png | Bin 0 -> 1240 bytes browser/branding/alpha/default24.png | Bin 0 -> 1368 bytes browser/branding/alpha/default256.png | Bin 0 -> 20481 bytes browser/branding/alpha/default32.png | Bin 0 -> 1956 bytes browser/branding/alpha/default48.png | Bin 0 -> 3067 bytes browser/branding/alpha/default512.png | Bin 0 -> 44907 bytes browser/branding/alpha/default64.png | Bin 0 -> 4318 bytes browser/branding/alpha/disk.icns | Bin 0 -> 1548786 bytes browser/branding/alpha/document.icns | Bin 0 -> 564054 bytes browser/branding/alpha/document.ico | Bin 0 -> 119671 bytes browser/branding/alpha/dsstore | Bin 0 -> 14340 bytes .../firefox.VisualElementsManifest.xml | 2 +- browser/branding/alpha/firefox.icns | Bin 0 -> 291096 bytes browser/branding/alpha/firefox.ico | Bin 0 -> 119941 bytes browser/branding/alpha/firefox.svg | 25 ++++++ browser/branding/alpha/firefox64.ico | Bin 0 -> 119941 bytes .../{official => alpha}/locales/en-US/brand.dtd | 8 +- .../{nightly => alpha}/locales/en-US/brand.ftl | 2 +- .../locales/en-US/brand.properties | 12 +-- browser/branding/{nightly => alpha}/locales/jar.mn | 7 +- .../branding/{nightly => alpha}/locales/moz.build | 2 - .../branding/{nightly/locales => alpha}/moz.build | 8 +- browser/branding/alpha/newtab.ico | Bin 0 -> 6518 bytes browser/branding/alpha/newwindow.ico | Bin 0 -> 6518 bytes browser/branding/alpha/pbmode.ico | Bin 0 -> 6518 bytes browser/branding/alpha/pref/firefox-branding.js | 34 ++++++++ browser/branding/alpha/stubinstaller/bgstub.jpg | Bin 0 -> 53597 bytes .../alpha/stubinstaller/installing_page.css | 61 +++++++++++++++ .../alpha/stubinstaller/profile_cleanup_page.css | 42 ++++++++++ browser/branding/alpha/wizHeader.bmp | Bin 0 -> 34254 bytes browser/branding/alpha/wizHeaderRTL.bmp | Bin 0 -> 34254 bytes browser/branding/alpha/wizWatermark.bmp | Bin 0 -> 206038 bytes browser/branding/branding-common.mozbuild | 2 + browser/branding/nightly/VisualElements_150.png | Bin 25470 -> 11666 bytes browser/branding/nightly/VisualElements_70.png | Bin 9590 -> 4273 bytes browser/branding/nightly/configure.sh | 8 +- .../nightly/content/identity-icons-brand.svg | 8 ++ browser/branding/nightly/content/jar.mn | 4 + browser/branding/nightly/content/tor-styles.css | 13 +++ browser/branding/nightly/default128.png | Bin 12392 -> 13686 bytes browser/branding/nightly/default16.png | Bin 756 -> 891 bytes browser/branding/nightly/default22.png | Bin 1146 -> 1377 bytes browser/branding/nightly/default24.png | Bin 1281 -> 1509 bytes browser/branding/nightly/default256.png | Bin 30546 -> 33587 bytes browser/branding/nightly/default32.png | Bin 1910 -> 2254 bytes browser/branding/nightly/default48.png | Bin 3606 -> 3789 bytes browser/branding/nightly/default512.png | Bin 0 -> 87830 bytes browser/branding/nightly/default64.png | Bin 4826 -> 5426 bytes browser/branding/nightly/document.icns | Bin 517716 -> 689723 bytes browser/branding/nightly/document.ico | Bin 47042 -> 124422 bytes .../nightly/firefox.VisualElementsManifest.xml | 2 +- browser/branding/nightly/firefox.icns | Bin 1014680 -> 642308 bytes browser/branding/nightly/firefox.ico | Bin 66730 -> 131711 bytes browser/branding/nightly/firefox.svg | 29 +++++++ browser/branding/nightly/firefox64.ico | Bin 38630 -> 131711 bytes browser/branding/nightly/locales/en-US/brand.dtd | 8 +- browser/branding/nightly/locales/en-US/brand.ftl | 2 +- .../nightly/locales/en-US/brand.properties | 10 +-- browser/branding/nightly/locales/jar.mn | 7 +- browser/branding/nightly/locales/moz.build | 2 - browser/branding/nightly/wizHeader.bmp | Bin 25820 -> 34254 bytes browser/branding/nightly/wizHeaderRTL.bmp | Bin 25820 -> 34254 bytes browser/branding/nightly/wizWatermark.bmp | Bin 154544 -> 206038 bytes browser/branding/official/VisualElements_150.png | Bin 23037 -> 7949 bytes browser/branding/official/VisualElements_70.png | Bin 8763 -> 3374 bytes browser/branding/official/configure.sh | 16 +--- .../official/content/identity-icons-brand.svg | 8 ++ browser/branding/official/content/jar.mn | 4 + browser/branding/official/content/tor-styles.css | 14 ++++ browser/branding/official/default128.png | Bin 13513 -> 9007 bytes browser/branding/official/default16.png | Bin 722 -> 839 bytes browser/branding/official/default22.png | Bin 1134 -> 1250 bytes browser/branding/official/default24.png | Bin 1312 -> 1405 bytes browser/branding/official/default256.png | Bin 32441 -> 19136 bytes browser/branding/official/default32.png | Bin 1948 -> 1965 bytes browser/branding/official/default48.png | Bin 3448 -> 3074 bytes browser/branding/official/default512.png | Bin 0 -> 40438 bytes browser/branding/official/default64.png | Bin 5459 -> 4196 bytes browser/branding/official/disk.icns | Bin 1525764 -> 172073 bytes browser/branding/official/document.icns | Bin 501145 -> 509227 bytes browser/branding/official/document.ico | Bin 45478 -> 119916 bytes .../official/firefox.VisualElementsManifest.xml | 2 +- browser/branding/official/firefox.icns | Bin 1021785 -> 259709 bytes browser/branding/official/firefox.ico | Bin 68328 -> 118595 bytes browser/branding/official/firefox.svg | 31 ++++++++ browser/branding/official/firefox64.ico | Bin 38630 -> 118595 bytes browser/branding/official/locales/en-US/brand.dtd | 8 +- .../official/locales/en-US/brand.properties | 10 +-- browser/branding/official/wizHeader.bmp | Bin 25820 -> 34254 bytes browser/branding/official/wizHeaderRTL.bmp | Bin 25820 -> 34254 bytes browser/branding/official/wizWatermark.bmp | Bin 154544 -> 206038 bytes browser/branding/tor-styles.inc.css | 87 +++++++++++++++++++++ browser/components/preferences/preferences.js | 5 +- browser/themes/shared/controlcenter/panel.inc.css | 4 +- .../shared/identity-block/identity-block.inc.css | 14 ++-- .../themes/shared/identity-block/onion-slash.svg | 5 ++ .../themes/shared/identity-block/onion-warning.svg | 4 + browser/themes/shared/identity-block/onion.svg | 4 + browser/themes/shared/jar.inc.mn | 5 ++ browser/themes/shared/onionPattern.css | 31 ++++++++ browser/themes/shared/onionPattern.inc.xhtml | 12 +++ browser/themes/shared/onionPattern.svg | 22 ++++++ .../images/aboutdebugging-firefox-aurora.svg | 35 ++++++++- .../themes/images/aboutdebugging-firefox-beta.svg | 35 ++++++++- .../themes/images/aboutdebugging-firefox-logo.svg | 11 ++- .../images/aboutdebugging-firefox-nightly.svg | 35 ++++++++- .../images/aboutdebugging-firefox-release.svg | 35 ++++++++- intl/l10n/L10nRegistry.jsm | 19 ++++- 131 files changed, 888 insertions(+), 113 deletions(-)
diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in index 54d6b43fe126f..8dd3a9a656612 100644 --- a/browser/app/Makefile.in +++ b/browser/app/Makefile.in @@ -102,5 +102,5 @@ ifdef MOZ_UPDATER mv -f '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' '$(dist_dest)/Contents/Library/LaunchServices' ln -s ../../../../Library/LaunchServices/org.mozilla.updater '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' endif - printf APPLMOZB > '$(dist_dest)/Contents/PkgInfo' + printf APPLTORB > '$(dist_dest)/Contents/PkgInfo' endif diff --git a/browser/app/macbuild/Contents/Info.plist.in b/browser/app/macbuild/Contents/Info.plist.in index 9ceaf88f15c1c..d8858e9f01bfa 100644 --- a/browser/app/macbuild/Contents/Info.plist.in +++ b/browser/app/macbuild/Contents/Info.plist.in @@ -179,7 +179,7 @@ <key>CFBundleShortVersionString</key> <string>@APP_VERSION@</string> <key>CFBundleSignature</key> - <string>MOZB</string> + <string>TORB</string> <key>CFBundleURLTypes</key> <array> <dict> diff --git a/browser/branding/alpha/VisualElements_150.png b/browser/branding/alpha/VisualElements_150.png new file mode 100644 index 0000000000000..fbf4af94d8139 Binary files /dev/null and b/browser/branding/alpha/VisualElements_150.png differ diff --git a/browser/branding/alpha/VisualElements_70.png b/browser/branding/alpha/VisualElements_70.png new file mode 100644 index 0000000000000..1add6b0e77ff5 Binary files /dev/null and b/browser/branding/alpha/VisualElements_70.png differ diff --git a/browser/branding/alpha/background.png b/browser/branding/alpha/background.png new file mode 100644 index 0000000000000..0a7e3088f4f02 Binary files /dev/null and b/browser/branding/alpha/background.png differ diff --git a/browser/branding/alpha/bgstub.jpg b/browser/branding/alpha/bgstub.jpg new file mode 100644 index 0000000000000..3b78c9498c93d Binary files /dev/null and b/browser/branding/alpha/bgstub.jpg differ diff --git a/browser/branding/alpha/bgstub_2x.jpg b/browser/branding/alpha/bgstub_2x.jpg new file mode 100644 index 0000000000000..c724d1803c264 Binary files /dev/null and b/browser/branding/alpha/bgstub_2x.jpg differ diff --git a/browser/branding/alpha/branding.nsi b/browser/branding/alpha/branding.nsi new file mode 100644 index 0000000000000..b37853b776438 --- /dev/null +++ b/browser/branding/alpha/branding.nsi @@ -0,0 +1,64 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# NSIS branding defines for nightly builds. +# The official release build branding.nsi is located in other-license/branding/firefox/ +# The unofficial build branding.nsi is located in browser/branding/unofficial/ + +# BrandFullNameInternal is used for some registry and file system values +# instead of BrandFullName and typically should not be modified. +!define BrandFullNameInternal "Nightly" +!define BrandFullName "Firefox Nightly" +!define CompanyName "mozilla.org" +!define URLInfoAbout "https://www.mozilla.org" +!define HelpLink "https://support.mozilla.org" + +!define URLStubDownloadX86 "https://download.mozilla.org/?os=win&lang=$%7BAB_CD%7D&product=firef..." +!define URLStubDownloadAMD64 "https://download.mozilla.org/?os=win64&lang=$%7BAB_CD%7D&product=fir..." +!define URLStubDownloadAArch64 "https://download.mozilla.org/?os=win64-aarch64&lang=$%7BAB_CD%7D&pro..." +!define URLManualDownload "https://www.mozilla.org/$%7BAB_CD%7D/firefox/installer-help/?channel=nightly..." +!define URLSystemRequirements "https://www.mozilla.org/firefox/system-requirements/" +!define Channel "nightly" + +# The installer's certificate name and issuer expected by the stub installer +!define CertNameDownload "Mozilla Corporation" +!define CertIssuerDownload "DigiCert SHA2 Assured ID Code Signing CA" + +# Dialog units are used so the UI displays correctly with the system's DPI +# settings. +!define PROFILE_CLEANUP_LABEL_TOP "35u" +!define PROFILE_CLEANUP_LABEL_LEFT "0" +!define PROFILE_CLEANUP_LABEL_WIDTH "100%" +!define PROFILE_CLEANUP_LABEL_HEIGHT "80u" +!define PROFILE_CLEANUP_LABEL_ALIGN "center" +!define PROFILE_CLEANUP_CHECKBOX_LEFT "center" +!define PROFILE_CLEANUP_CHECKBOX_WIDTH "100%" +!define PROFILE_CLEANUP_BUTTON_LEFT "center" +!define INSTALL_BLURB_TOP "137u" +!define INSTALL_BLURB_WIDTH "60u" +!define INSTALL_FOOTER_TOP "-48u" +!define INSTALL_FOOTER_WIDTH "250u" +!define INSTALL_INSTALLING_TOP "70u" +!define INSTALL_INSTALLING_LEFT "0" +!define INSTALL_INSTALLING_WIDTH "100%" +!define INSTALL_PROGRESS_BAR_TOP "112u" +!define INSTALL_PROGRESS_BAR_LEFT "20%" +!define INSTALL_PROGRESS_BAR_WIDTH "60%" +!define INSTALL_PROGRESS_BAR_HEIGHT "12u" + +!define PROFILE_CLEANUP_CHECKBOX_TOP_MARGIN "20u" +!define PROFILE_CLEANUP_BUTTON_TOP_MARGIN "20u" +!define PROFILE_CLEANUP_BUTTON_X_PADDING "40u" +!define PROFILE_CLEANUP_BUTTON_Y_PADDING "4u" + +# Font settings that can be customized for each channel +!define INSTALL_HEADER_FONT_SIZE 28 +!define INSTALL_HEADER_FONT_WEIGHT 400 +!define INSTALL_INSTALLING_FONT_SIZE 28 +!define INSTALL_INSTALLING_FONT_WEIGHT 400 + +# UI Colors that can be customized for each channel +!define COMMON_TEXT_COLOR 0xFFFFFF +!define COMMON_BACKGROUND_COLOR 0x000000 +!define INSTALL_INSTALLING_TEXT_COLOR 0xFFFFFF diff --git a/browser/branding/nightly/locales/moz.build b/browser/branding/alpha/configure.sh similarity index 51% copy from browser/branding/nightly/locales/moz.build copy to browser/branding/alpha/configure.sh index fff7035065b0a..243091484f758 100644 --- a/browser/branding/nightly/locales/moz.build +++ b/browser/branding/alpha/configure.sh @@ -1,9 +1,5 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DEFINES["MOZ_DISTRIBUTION_ID_UNQUOTED"] = CONFIG["MOZ_DISTRIBUTION_ID"] - -JAR_MANIFESTS += ["jar.mn"] +MOZ_APP_DISPLAYNAME="Tor Browser" diff --git a/browser/branding/alpha/content/about-logo.png b/browser/branding/alpha/content/about-logo.png new file mode 100644 index 0000000000000..7d705be61dfd7 Binary files /dev/null and b/browser/branding/alpha/content/about-logo.png differ diff --git a/browser/branding/alpha/content/about-logo.svg b/browser/branding/alpha/content/about-logo.svg new file mode 100644 index 0000000000000..caf587e212b6d --- /dev/null +++ b/browser/branding/alpha/content/about-logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512"><defs><radialGradient id="h" cx="-9235.977" cy="-9835.981" r="118.081" gradientTransform="matrix(6.201 0 0 6.2 57644.994 60908.8)" gradientUnits="userSpaceOnUse"><stop offset=".126" stop-color="#3fe1b0"/><stop offset=".429" stop-color="#0df"/><stop offset=".479" stop-color="#1ec1ff"/><stop offset=".624" stop-color="#7077ff"/><stop offset=".69" stop-color="#9059ff"/><stop offset=".904" stop-color="#b833e1"/></radialGradient> [...] \ No newline at end of file diff --git a/browser/branding/alpha/content/about-logo@2x.png b/browser/branding/alpha/content/about-logo@2x.png new file mode 100644 index 0000000000000..193c856f3e8c5 Binary files /dev/null and b/browser/branding/alpha/content/about-logo@2x.png differ diff --git a/browser/branding/alpha/content/about-wordmark.svg b/browser/branding/alpha/content/about-wordmark.svg new file mode 100644 index 0000000000000..6f71130b417df --- /dev/null +++ b/browser/branding/alpha/content/about-wordmark.svg @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" width="270px" height="48px" viewBox="0 0 270 48"> + <path fill="#fff" d="M75.5,11.8V7.9c0-2.2,1.2-3.5,3.1-3.5c1,0,1.8,0.3,3,0.9l1.8-3.5c-1.7-1-3.5-1.4-5.7-1.4 + C73.2,0.3,70,2.8,70,8c0,2.3,0.2,3.7,0.2,3.7h-2.5v3.8H70V37h5.4V15.6h5.1l1.4-3.8H75.5z M92.3,11.2c-6.7,0-11,5.2-11,13.3 + c0,8.1,4.3,13.2,11.1,13.2c6.8,0,11.2-5,11.2-13.2C103.6,16.5,99.5,11.2,92.3,11.2z M92.5,33.6c-3.3,0-5.1-2.1-5.1-9.5 + c0-6.1,1.5-8.8,5-8.8c3.2,0,5.2,2.1,5.2,9.3C97.6,30.9,95.8,33.6,92.5,33.6z M43.7,11.1c-2.5,0-4.4,1.3-6.4,4c0-1.4-0.3-2.8-0.9-4 + l-5,1.3c0.6,1.6,0.9,3.6,0.9,6.8V37h5.5V19.9c0.5-2,2.4-3.7,4.7-3.7c0.6,0,1,0.1,1.6,0.4l1.7-5.1C45,11.2,44.5,11.1,43.7,11.1z + M0,37h5.7V21.2h9.6v-4.6H5.7V7.2h11.8l0.7-4.7H0V37z M21.4,37h5.5V11.2l-5.5,1V37z M24.2,0.7c-2,0-3.6,1.6-3.6,3.7 + c0,2,1.5,3.6,3.5,3.6c2,0,3.7-1.6,3.7-3.6C27.8,2.3,26.2,0.7,24.2,0.7z M125.2,11.8h-6.4c-0.7,1.1-3.3,6.1-4,7.7 + c-1.2-2.3-3.4-6.3-4.6-8.2l-5.9,1.2l7.3,10.8L102.2,37h6.9c0.9-1.4,4.5-7.5,5.5-9.4c0.5,0.9,4.6,8,5.5,9.4h6.9l-9.2-13.8L125.2,11.8 + z M62.7,13.8c-2.1-1.9-4.4-2.6-6.9-2.6c-3.2,0-5.7,1-7.7,3.4C45.9,17.1,45,20,45,24.5c0,8.1,4.5,13.2,11.6,13.2 + c3.4,0,6.4-1.1,9.1-3.3L63.4,31c-1.9,1.6-3.9,2.5-6.3,2.5c-4.9,0-6.2-3.7-6.2-7.2v-0.4H66v-1.2C66,18.9,64.9,15.8,62.7,13.8z + M51,21.8c0-4.1,1.7-6.5,4.8-6.5c2.8,0,4.5,2.4,4.5,6.5H51z M198.5,14.3l-2.4-2.4c-1.2,0.8-2.2,1.1-3.5,1.1c-3,0-3.8-1.4-7.6-1.4 + c-5.4,0-9.2,3.4-9.2,8.4c0,3.3,2.2,6.1,5.6,7.2c-3.4,1-4.5,2.2-4.5,4.3c0,2.2,1.8,3.6,4.7,3.6h3.8c2.5,0,3.9,0.2,4.9,0.9 + c0.9,0.6,1.4,1.6,1.4,3c0,3.1-2.2,4.4-6,4.4c-2,0-3.8-0.5-5.1-1.2c-0.9-0.6-1.5-1.6-1.5-2.9c0-0.8,0.3-1.7,0.7-2.2l-4.1,0.4 + c-0.3,1-0.5,1.7-0.5,2.6c0,3.5,3,6.4,10.8,6.4c6.1,0,9.9-2.5,9.9-7.9c0-2.1-0.8-3.9-2.7-5.3c-1.5-1.1-3.1-1.4-6-1.4h-4 + c-1.3,0-2-0.5-2-1.2c0-0.8,1.1-1.7,4.5-2.9c1.8,0,3.4-0.3,4.7-1.1c2.3-1.4,3.7-4.1,3.7-6.8c0-1.6-0.5-3-1.5-4.3 + c0.4,0.2,1.1,0.3,1.7,0.3C195.8,15.8,196.9,15.4,198.5,14.3z M185,24.8c-3.1,0-4.8-1.7-4.8-4.8c0-3.5,1.6-5.1,4.7-5.1 + c3.3,0,4.6,1.5,4.6,4.9C189.5,23.1,188,24.8,185,24.8z M168.6,1.3c-1.7,0-3,1.4-3,3.1c0,1.7,1.4,3,3,3c1.7,0,3.1-1.3,3.1-3 + C171.6,2.7,170.3,1.3,168.6,1.3z M245.7,34.5c-1.1,0-1.4-0.6-1.4-2.5V6.5c0-3.8-0.6-5.9-0.6-5.9l-3.9,0.8c0,0,0.6,1.9,0.6,5.1v26.4 + c0,1.8,0.4,2.8,1.2,3.5c0.7,0.7,1.7,1,2.9,1c1,0,1.5-0.1,2.5-0.5l-0.8-2.5C246.2,34.4,245.8,34.5,245.7,34.5z M212.7,11.6 + c-3.2,0-6.1,1.8-8.3,3.9c0,0,0.2-1.8,0.2-3.4V6.3c0-3.8-0.7-5.9-0.7-5.9L200,1.1c0,0,0.7,1.9,0.7,5.1V37h3.9V19.3 + c2.1-2.7,4.9-4.2,7.2-4.2c1.3,0,2.3,0.4,2.9,1c0.7,0.7,0.9,1.8,0.9,3.7V37h3.8V19.1c0-1.8-0.1-2.6-0.4-3.6 + C218.4,13.2,215.7,11.6,212.7,11.6z M265.4,12.1l-4.9,16.4c-0.6,2-1.6,5.2-1.6,5.2s-0.7-3.9-1.5-6.2l-5.1-16.2l-3.9,1.3l5.4,15.6 + c0.8,2.5,2.2,7.4,2.5,9l1.6-0.3c-1.3,5.1-2.5,6.7-5.7,7.6l1.2,2.7c4.4-1,6.4-4.3,8-9.3l8.6-25.8H265.4z M234.9,15l1.2-2.9h-6.2 + c0-3.3,0.5-7.2,0.5-7.2l-4.1,0.9c0,0-0.4,3.9-0.4,6.3h-3.2V15h3.2v17.1c0,2.5,0.7,4.1,2.4,5c0.9,0.4,1.9,0.7,3.3,0.7 + c1.8,0,3.1-0.4,4.4-1l-0.6-2.5c-0.7,0.3-1.3,0.5-2.4,0.5c-2.4,0-3.2-0.9-3.2-3.7V15H234.9z M166.5,37h4.1V11.5l-4.1,0.6V37z + M156.8,21.3c0,5,0.4,10.5,0.4,10.5s-1.4-3.8-3.2-7.2L142.7,2.7h-4.8V37h4.2l-0.2-19.9c0-4.5-0.4-9.3-0.4-9.3s1.7,4.1,3.9,8.2l11,21 + h4.3V2.7h-4L156.8,21.3z M128.3,12.9c-0.3-0.1-0.7-0.1-1-0.1v2.3h0.3v-1c0.3,0,0.7,1,0.7,1s0.2,0,0.4,0c-0.2-0.3-0.3-0.7-0.6-1 + C128.8,14.1,128.9,13.1,128.3,12.9z M127.6,13.8v-0.7c0,0,0.7,0,0.7,0.3C128.3,13.9,127.8,13.9,127.6,13.8z M128,12 + c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S129.1,12,128,12z M128,15.5c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5 + S128.8,15.5,128,15.5z"/> +</svg> diff --git a/browser/branding/alpha/content/about.png b/browser/branding/alpha/content/about.png new file mode 100644 index 0000000000000..3b93625ddd70f Binary files /dev/null and b/browser/branding/alpha/content/about.png differ diff --git a/browser/branding/alpha/content/aboutDialog.css b/browser/branding/alpha/content/aboutDialog.css new file mode 100644 index 0000000000000..293b5f493f3f3 --- /dev/null +++ b/browser/branding/alpha/content/aboutDialog.css @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#aboutDialogContainer { + background-color: #20123a; + color: #fff; +} + +#clientBox { + padding: 10px 0 15px; +} + +#leftBox { + background-image: url("chrome://branding/content/about-logo.png"); + background-repeat: no-repeat; + background-size: 192px auto; + background-position: center 20%; + /* min-width and min-height create room for the logo */ + min-width: 210px; + min-height: 210px; + margin-top: 20px; + margin-inline-start: 30px; +} + +@media (min-resolution: 2dppx) { + #leftBox { + background-image: url("chrome://branding/content/about-logo@2x.png"); + } +} + +.text-link { + color: #fff !important; + text-decoration: underline; +} + +.text-link:-moz-focusring { + border-color: #fff; +} + +#rightBox { + margin-inline: 30px; + padding-top: 64px; +} + +#bottomBox { + background-color: hsla(235, 43%, 10%, .5); + padding: 15px 10px 15px; +} diff --git a/browser/branding/alpha/content/aboutlogins.svg b/browser/branding/alpha/content/aboutlogins.svg new file mode 100644 index 0000000000000..f4b6a3fc41b79 --- /dev/null +++ b/browser/branding/alpha/content/aboutlogins.svg @@ -0,0 +1,59 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="80" width="460" viewBox="0 0 460 80"> + <defs> + <linearGradient id="a" x1="57.63" y1="9.47" x2="21.37" y2="72.26" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#ff980e"/> + <stop offset=".11" stop-color="#ff851b"/> + <stop offset=".57" stop-color="#ff3750"/> + <stop offset=".8" stop-color="#f92261"/> + <stop offset="1" stop-color="#f5156c"/> + </linearGradient> + <linearGradient id="b" x1="57.31" y1="-.8" x2="27.68" y2="69.03" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#fff261" stop-opacity=".8"/> + <stop offset=".06" stop-color="#fff261" stop-opacity=".68"/> + <stop offset=".19" stop-color="#fff261" stop-opacity=".48"/> + <stop offset=".31" stop-color="#fff261" stop-opacity=".31"/> + <stop offset=".42" stop-color="#fff261" stop-opacity=".17"/> + <stop offset=".53" stop-color="#fff261" stop-opacity=".08"/> + <stop offset=".63" stop-color="#fff261" stop-opacity=".02"/> + <stop offset=".72" stop-color="#fff261" stop-opacity="0"/> + </linearGradient> + <linearGradient id="c" x1="71.71" y1="75.85" x2="71.71" y2="28.29" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#0090ed"/> + <stop offset=".5" stop-color="#9059ff"/> + <stop offset=".81" stop-color="#b833e1"/> + </linearGradient> + <linearGradient id="d" x1="17.89" y1="78.48" x2="48.5" y2="26.39" gradientUnits="userSpaceOnUse"> + <stop offset=".02" stop-color="#0090ed"/> + <stop offset=".49" stop-color="#9059ff"/> + <stop offset="1" stop-color="#b833e1"/> + </linearGradient> + <linearGradient id="e" x1="21.87" y1="58.41" x2="4.02" y2="40.56" gradientUnits="userSpaceOnUse"> + <stop offset=".14" stop-color="#592acb" stop-opacity="0"/> + <stop offset=".33" stop-color="#542bc8" stop-opacity=".03"/> + <stop offset=".53" stop-color="#462fbf" stop-opacity=".11"/> + <stop offset=".74" stop-color="#2f35b1" stop-opacity=".25"/> + <stop offset=".95" stop-color="#0f3d9c" stop-opacity=".44"/> + <stop offset="1" stop-color="#054096" stop-opacity=".5"/> + </linearGradient> + <linearGradient id="f" x1="75.86" y1="38.71" x2="66.87" y2="54.27" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#722291" stop-opacity=".5"/> + <stop offset=".5" stop-color="#b833e1" stop-opacity="0"/> + </linearGradient> + <linearGradient id="g" x1="56.84" y1="60.96" x2="46.4" y2="72.73" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#054096" stop-opacity=".5"/> + <stop offset=".03" stop-color="#0f3d9c" stop-opacity=".44"/> + <stop offset=".17" stop-color="#2f35b1" stop-opacity=".25"/> + <stop offset=".3" stop-color="#462fbf" stop-opacity=".11"/> + <stop offset=".43" stop-color="#542bc8" stop-opacity=".03"/> + <stop offset=".56" stop-color="#592acb" stop-opacity="0"/> + </linearGradient> + </defs> + <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.3 [...] + <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.3 [...] + <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#c)"/> + <path d="M55.45 60.56c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .21 17.27 309.82 309.82 0 0 0 22.42 21.97A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.37 3.37 0 0 0 0-4.86z" fill="url(#d)"/> + <path d="M7.78 54.53c2.92 3.17 5.83 6.2 8.81 9.16l1.19-1.94c1-1.59 2-3.15 3.07-4.71-3.85-3.91-7.66-7.95-11.54-12.3a7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .18 17.22z" fill="url(#e)" opacity=".9"/> + <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#f)"/> + <path d="M58.51 63.47l-3.06-2.91c-3.4 3.37-6.94 6.71-10.72 10.13a7.71 7.71 0 0 1-6.07 1.48v7.77c.44 0 .88.06 1.33.06a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.36 3.36 0 0 0-.03-4.86z" fill="url(#g)" opacity=".9"/> + <path d="M97 56.15h6.25v-13h14.44v-5.8h-14.48v-7.41h14.44v-5.89H97zm28.35-34.38a3.79 3.79 0 0 0-3.87 3.95 3.9 3.9 0 0 0 7.79 0 3.77 3.77 0 0 0-3.96-3.95zm-3.08 34.38h6.21V32.41h-6.21zm17-20.09v-3.65h-6v23.74h6V43.62c0-4 2-5.58 5.15-5.58a5.59 5.59 0 0 1 3.17.83l2.2-6a8.78 8.78 0 0 0-4-.92c-3 .05-5.38 1.29-6.52 4.11zm23.42-4.14a12.27 12.27 0 0 0-12.46 12.41c0 6.9 4.93 12.31 12.59 12.31a12.5 12.5 0 0 0 11-5.5l-5-2.9a6.5 6.5 0 0 1-5.9 3.17 6.61 6.61 0 0 1-6.83-5H175V44.1a11.84 11.84 0 0 0- [...] +</svg> diff --git a/browser/branding/alpha/content/firefox-wordmark.svg b/browser/branding/alpha/content/firefox-wordmark.svg new file mode 100644 index 0000000000000..65270a3cd9a96 --- /dev/null +++ b/browser/branding/alpha/content/firefox-wordmark.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="172" height="42"><path fill="context-fill #20123a" d="M.19 2.82h25.72v7H7.57v9.43h18.34v6.9H7.57v15.14H.19zM34.65.13a4.14 4.14 0 0 1 4.27 4.33 4.12 4.12 0 0 1-4.32 4.32 4.09 4.09 0 0 1-4.27-4.22A4.27 4.27 0 0 1 34.65.13zM31 12.83h7.27v28.46H31zm28.35 7.91a5.89 5.89 0 0 0-3.53-1.27c-3 0-4.64 1.9-4.64 6.06v15.76H44V12.83h6.9v4.11a6.79 6.79 0 0 1 6.8-4.37A8.69 8.69 0 0 1 62.53 14zm3 6.48c0-8.17 6.06-15 14.65-15s14.59 6.06 14.59 14.49v3H69.48c.7 [...] \ No newline at end of file diff --git a/browser/branding/alpha/content/identity-icons-brand.svg b/browser/branding/alpha/content/identity-icons-brand.svg new file mode 100644 index 0000000000000..382a061774aaa --- /dev/null +++ b/browser/branding/alpha/content/identity-icons-brand.svg @@ -0,0 +1,8 @@ +<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <g clip-rule="evenodd" fill-rule="evenodd"> + <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/> + <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/> + <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/> + </g> + <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/> +</svg> \ No newline at end of file diff --git a/browser/branding/nightly/content/jar.mn b/browser/branding/alpha/content/jar.mn similarity index 80% copy from browser/branding/nightly/content/jar.mn copy to browser/branding/alpha/content/jar.mn index 6280b54882558..93ff6ecf736b3 100644 --- a/browser/branding/nightly/content/jar.mn +++ b/browser/branding/alpha/content/jar.mn @@ -16,4 +16,8 @@ browser.jar: content/branding/icon48.png (../default48.png) content/branding/icon64.png (../default64.png) content/branding/icon128.png (../default128.png) + content/branding/icon256.png (../default256.png) + content/branding/icon512.png (../default512.png) + content/branding/identity-icons-brand.svg content/branding/aboutDialog.css +* content/branding/tor-styles.css diff --git a/browser/branding/nightly/locales/moz.build b/browser/branding/alpha/content/moz.build similarity index 81% copy from browser/branding/nightly/locales/moz.build copy to browser/branding/alpha/content/moz.build index fff7035065b0a..d988c0ff9b162 100644 --- a/browser/branding/nightly/locales/moz.build +++ b/browser/branding/alpha/content/moz.build @@ -4,6 +4,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DEFINES["MOZ_DISTRIBUTION_ID_UNQUOTED"] = CONFIG["MOZ_DISTRIBUTION_ID"] - JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/branding/alpha/content/tor-styles.css b/browser/branding/alpha/content/tor-styles.css new file mode 100644 index 0000000000000..14c1915ef871d --- /dev/null +++ b/browser/branding/alpha/content/tor-styles.css @@ -0,0 +1,13 @@ +%include ../../tor-styles.inc.css + +/* default theme*/ +:root, +/* light theme*/ +:root:-moz-lwtheme-darktext { + --tor-branding-color: var(--teal-70); +} + +/* dark theme */ +:root:-moz-lwtheme-brighttext { + --tor-branding-color: var(--teal-60); +} \ No newline at end of file diff --git a/browser/branding/alpha/default128.png b/browser/branding/alpha/default128.png new file mode 100644 index 0000000000000..fbc27b91d118b Binary files /dev/null and b/browser/branding/alpha/default128.png differ diff --git a/browser/branding/alpha/default16.png b/browser/branding/alpha/default16.png new file mode 100644 index 0000000000000..3a4e1b679b278 Binary files /dev/null and b/browser/branding/alpha/default16.png differ diff --git a/browser/branding/alpha/default22.png b/browser/branding/alpha/default22.png new file mode 100644 index 0000000000000..4feb2dbd400cb Binary files /dev/null and b/browser/branding/alpha/default22.png differ diff --git a/browser/branding/alpha/default24.png b/browser/branding/alpha/default24.png new file mode 100644 index 0000000000000..4387f97e3d625 Binary files /dev/null and b/browser/branding/alpha/default24.png differ diff --git a/browser/branding/alpha/default256.png b/browser/branding/alpha/default256.png new file mode 100644 index 0000000000000..844f1a0323ee0 Binary files /dev/null and b/browser/branding/alpha/default256.png differ diff --git a/browser/branding/alpha/default32.png b/browser/branding/alpha/default32.png new file mode 100644 index 0000000000000..679f5f9db43fc Binary files /dev/null and b/browser/branding/alpha/default32.png differ diff --git a/browser/branding/alpha/default48.png b/browser/branding/alpha/default48.png new file mode 100644 index 0000000000000..85f0253d88cac Binary files /dev/null and b/browser/branding/alpha/default48.png differ diff --git a/browser/branding/alpha/default512.png b/browser/branding/alpha/default512.png new file mode 100644 index 0000000000000..b12f58b88bb4c Binary files /dev/null and b/browser/branding/alpha/default512.png differ diff --git a/browser/branding/alpha/default64.png b/browser/branding/alpha/default64.png new file mode 100644 index 0000000000000..c48f1c5bf4eed Binary files /dev/null and b/browser/branding/alpha/default64.png differ diff --git a/browser/branding/alpha/disk.icns b/browser/branding/alpha/disk.icns new file mode 100644 index 0000000000000..866d93a43bc8a Binary files /dev/null and b/browser/branding/alpha/disk.icns differ diff --git a/browser/branding/alpha/document.icns b/browser/branding/alpha/document.icns new file mode 100644 index 0000000000000..7fbfffe2228e4 Binary files /dev/null and b/browser/branding/alpha/document.icns differ diff --git a/browser/branding/alpha/document.ico b/browser/branding/alpha/document.ico new file mode 100644 index 0000000000000..45aa08bb16587 Binary files /dev/null and b/browser/branding/alpha/document.ico differ diff --git a/browser/branding/alpha/dsstore b/browser/branding/alpha/dsstore new file mode 100644 index 0000000000000..6b82c923a6624 Binary files /dev/null and b/browser/branding/alpha/dsstore differ diff --git a/browser/branding/official/firefox.VisualElementsManifest.xml b/browser/branding/alpha/firefox.VisualElementsManifest.xml similarity index 93% copy from browser/branding/official/firefox.VisualElementsManifest.xml copy to browser/branding/alpha/firefox.VisualElementsManifest.xml index 85e09dd7a910f..a71938708affa 100644 --- a/browser/branding/official/firefox.VisualElementsManifest.xml +++ b/browser/branding/alpha/firefox.VisualElementsManifest.xml @@ -8,5 +8,5 @@ Square150x150Logo='browser\VisualElements\VisualElements_150.png' Square70x70Logo='browser\VisualElements\VisualElements_70.png' ForegroundText='light' - BackgroundColor='#20123a'/> + BackgroundColor='#1c191d'/> </Application> diff --git a/browser/branding/alpha/firefox.icns b/browser/branding/alpha/firefox.icns new file mode 100644 index 0000000000000..baad294f8497c Binary files /dev/null and b/browser/branding/alpha/firefox.icns differ diff --git a/browser/branding/alpha/firefox.ico b/browser/branding/alpha/firefox.ico new file mode 100644 index 0000000000000..e25514996d37f Binary files /dev/null and b/browser/branding/alpha/firefox.ico differ diff --git a/browser/branding/alpha/firefox.svg b/browser/branding/alpha/firefox.svg new file mode 100644 index 0000000000000..250c7adea0d68 --- /dev/null +++ b/browser/branding/alpha/firefox.svg @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="50%" y1="3.27248873%" x2="50%" y2="97.1599968%" id="linearGradient-1"> + <stop stop-color="#00FEFF" offset="0%"></stop> + <stop stop-color="#0BE67D" offset="100%"></stop> + </linearGradient> + <path d="M25,25 C152.50841,25 255.874399,127.979815 255.874399,255.011855 C255.874399,382.043895 152.50841,485.02371 25,485.02371 L25,25 Z" id="path-2"></path> + <filter x="-20.8%" y="-8.7%" width="134.7%" height="117.4%" filterUnits="objectBoundingBox" id="filter-3"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="12" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.0872579578 0 0 0 0 0.00490370801 0 0 0 0 0.234933036 0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Alpha" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g> + <circle id="background" fill-opacity="0.9" fill="#030004" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256 [...] + <g id="half" transform="translate(140.437200, 255.011855) scale(-1, 1) translate(-140.437200, -255.011855) "> + <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use> + <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/browser/branding/alpha/firefox64.ico b/browser/branding/alpha/firefox64.ico new file mode 100644 index 0000000000000..e25514996d37f Binary files /dev/null and b/browser/branding/alpha/firefox64.ico differ diff --git a/browser/branding/official/locales/en-US/brand.dtd b/browser/branding/alpha/locales/en-US/brand.dtd similarity index 68% copy from browser/branding/official/locales/en-US/brand.dtd copy to browser/branding/alpha/locales/en-US/brand.dtd index d094ad0f8d018..0b15c9978e017 100644 --- a/browser/branding/official/locales/en-US/brand.dtd +++ b/browser/branding/alpha/locales/en-US/brand.dtd @@ -2,10 +2,10 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<!ENTITY brandShorterName "Firefox"> -<!ENTITY brandShortName "Firefox"> -<!ENTITY brandFullName "Mozilla Firefox"> +<!ENTITY brandShorterName "Tor Browser"> +<!ENTITY brandShortName "Tor Browser"> +<!ENTITY brandFullName "Tor Browser"> <!-- LOCALIZATION NOTE (brandProductName): This brand name can be used in messages where the product name needs to remain unchanged across different versions (Nightly, Beta, etc.). --> -<!ENTITY brandProductName "Firefox"> +<!ENTITY brandProductName "Tor Browser"> diff --git a/browser/branding/nightly/locales/en-US/brand.ftl b/browser/branding/alpha/locales/en-US/brand.ftl similarity index 90% copy from browser/branding/nightly/locales/en-US/brand.ftl copy to browser/branding/alpha/locales/en-US/brand.ftl index f633bc269f58a..e970d32cb43e4 100644 --- a/browser/branding/nightly/locales/en-US/brand.ftl +++ b/browser/branding/alpha/locales/en-US/brand.ftl @@ -23,4 +23,4 @@ # remain unchanged across different versions (Nightly, Beta, etc.). -brand-product-name = Firefox -vendor-short-name = Mozilla -trademarkInfo = { " " } +trademarkInfo = Firefox and the Firefox logos are trademarks of the Mozilla Foundation. diff --git a/browser/branding/official/locales/en-US/brand.properties b/browser/branding/alpha/locales/en-US/brand.properties similarity index 69% copy from browser/branding/official/locales/en-US/brand.properties copy to browser/branding/alpha/locales/en-US/brand.properties index 8ade3cb6e5bb9..e96b063b90345 100644 --- a/browser/branding/official/locales/en-US/brand.properties +++ b/browser/branding/alpha/locales/en-US/brand.properties @@ -2,11 +2,13 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-brandShorterName=Firefox -brandShortName=Firefox -brandFullName=Mozilla Firefox +brandShorterName=Tor Browser +brandShortName=Tor Browser +brandFullName=Tor Browser # LOCALIZATION NOTE(brandProductName): # This brand name can be used in messages where the product name needs to # remain unchanged across different versions (Nightly, Beta, etc.). -brandProductName=Firefox -vendorShortName=Mozilla +brandProductName=Tor Browser +vendorShortName=Tor Project + +syncBrandShortName=Sync diff --git a/browser/branding/nightly/locales/jar.mn b/browser/branding/alpha/locales/jar.mn similarity index 58% copy from browser/branding/nightly/locales/jar.mn copy to browser/branding/alpha/locales/jar.mn index c04a7a1cf0f0a..d13c2110148f4 100644 --- a/browser/branding/nightly/locales/jar.mn +++ b/browser/branding/alpha/locales/jar.mn @@ -4,10 +4,9 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
[localization] @AB_CD@.jar: - branding (en-US/**/*.ftl) + branding (%*.ftl)
@AB_CD@.jar: % locale branding @AB_CD@ %locale/branding/ -# Nightly branding only exists in en-US - locale/branding/brand.dtd (en-US/brand.dtd) - locale/branding/brand.properties (en-US/brand.properties) + locale/branding/brand.dtd (%brand.dtd) + locale/branding/brand.properties (%brand.properties) diff --git a/browser/branding/nightly/locales/moz.build b/browser/branding/alpha/locales/moz.build similarity index 81% copy from browser/branding/nightly/locales/moz.build copy to browser/branding/alpha/locales/moz.build index fff7035065b0a..d988c0ff9b162 100644 --- a/browser/branding/nightly/locales/moz.build +++ b/browser/branding/alpha/locales/moz.build @@ -4,6 +4,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DEFINES["MOZ_DISTRIBUTION_ID_UNQUOTED"] = CONFIG["MOZ_DISTRIBUTION_ID"] - JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/branding/nightly/locales/moz.build b/browser/branding/alpha/moz.build similarity index 68% copy from browser/branding/nightly/locales/moz.build copy to browser/branding/alpha/moz.build index fff7035065b0a..dd081ac44496d 100644 --- a/browser/branding/nightly/locales/moz.build +++ b/browser/branding/alpha/moz.build @@ -4,6 +4,10 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DEFINES["MOZ_DISTRIBUTION_ID_UNQUOTED"] = CONFIG["MOZ_DISTRIBUTION_ID"] +DIRS += ["content", "locales"]
-JAR_MANIFESTS += ["jar.mn"] +DIST_SUBDIR = "browser" +export("DIST_SUBDIR") + +include("../branding-common.mozbuild") +FirefoxBranding() diff --git a/browser/branding/alpha/newtab.ico b/browser/branding/alpha/newtab.ico new file mode 100644 index 0000000000000..a9b37c08c6e18 Binary files /dev/null and b/browser/branding/alpha/newtab.ico differ diff --git a/browser/branding/alpha/newwindow.ico b/browser/branding/alpha/newwindow.ico new file mode 100644 index 0000000000000..55372077102c4 Binary files /dev/null and b/browser/branding/alpha/newwindow.ico differ diff --git a/browser/branding/alpha/pbmode.ico b/browser/branding/alpha/pbmode.ico new file mode 100644 index 0000000000000..47677c13fba68 Binary files /dev/null and b/browser/branding/alpha/pbmode.ico differ diff --git a/browser/branding/alpha/pref/firefox-branding.js b/browser/branding/alpha/pref/firefox-branding.js new file mode 100644 index 0000000000000..792134ab45d76 --- /dev/null +++ b/browser/branding/alpha/pref/firefox-branding.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file contains branding-specific prefs. + +pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD..."); +pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/"); +pref("startup.homepage_welcome_url.additional", ""); +// The time interval between checks for a new version (in seconds) +pref("app.update.interval", 7200); // 2 hours +// Give the user x seconds to react before showing the big UI. default=12 hours +pref("app.update.promptWaitTime", 43200); +// URL user can browse to manually if for some reason all update installation +// attempts fail. +pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/nightly/"); +// A default value for the "More information about this update" link +// supplied in the "An update is available" page of the update wizard. +pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/nightly/notes/"); + +pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source=..."); + +// The number of days a binary is permitted to be old +// without checking for an update. This assumes that +// app.update.checkInstallTime is true. +pref("app.update.checkInstallTime.days", 2); + +// Give the user x seconds to reboot before showing a badge on the hamburger +// button. default=immediately +pref("app.update.badgeWaitTime", 0); + +// Number of usages of the web console. +// If this is less than 5, then pasting code into the web console is disabled +pref("devtools.selfxss.count", 5); diff --git a/browser/branding/alpha/stubinstaller/bgstub.jpg b/browser/branding/alpha/stubinstaller/bgstub.jpg new file mode 100644 index 0000000000000..891036a4fe354 Binary files /dev/null and b/browser/branding/alpha/stubinstaller/bgstub.jpg differ diff --git a/browser/branding/alpha/stubinstaller/installing_page.css b/browser/branding/alpha/stubinstaller/installing_page.css new file mode 100644 index 0000000000000..8044838c79f5d --- /dev/null +++ b/browser/branding/alpha/stubinstaller/installing_page.css @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +body { + color: white; +} + +#label, +#progress_background, +#blurb { + text-align: center; + margin: 20px 30px; +} + +#label { + font-size: 40px; + margin-top: 100px; + margin-bottom: 20px; +} + +#progress_background { + margin: 0 auto; + width: 60%; + height: 24px; + background-color: white; +} + +body.high-contrast #progress_background { + outline: solid; +} + +#progress_bar { + margin: 0; + width: 0%; + height: 100%; + background-color: #00AAFF; +} + +/* In high contrast mode, fill the entire progress bar with its border. */ +body.high-contrast #progress_bar { + /* This border should be the height of progress_background. */ + border-top: 24px solid; + box-sizing: border-box; +} + +/* This layout doesn't want the header or content text. */ +#header, #content { + display: none; +} + +#blurb { + font-size: 20px; +} + +/* The footer goes in the bottom right corner. */ +#footer { + position: fixed; + right: 50px; + bottom: 59px; +} diff --git a/browser/branding/alpha/stubinstaller/profile_cleanup_page.css b/browser/branding/alpha/stubinstaller/profile_cleanup_page.css new file mode 100644 index 0000000000000..2d9c3ad1891e0 --- /dev/null +++ b/browser/branding/alpha/stubinstaller/profile_cleanup_page.css @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +body { + color: white; +} + +#header, +#refreshCheckboxContainer, +#refreshButtonContainer { + text-align: center; + margin-left: 40px; + margin-right: 40px; + margin-bottom: 30px; +} + +#header { + font-size: 35px; + font-weight: normal; + margin-top: 45px; +} + +#refreshCheckbox { + vertical-align: middle; +} + +#checkboxLabel { + font-size: 13px; +} + +#refreshButton { + padding: 8px 40px; + font-size: 15px; +} + +/* The footer goes in the bottom right corner. */ +#footer { + position: fixed; + right: 50px; + bottom: 59px; +} diff --git a/browser/branding/alpha/wizHeader.bmp b/browser/branding/alpha/wizHeader.bmp new file mode 100644 index 0000000000000..a754d2db1e114 Binary files /dev/null and b/browser/branding/alpha/wizHeader.bmp differ diff --git a/browser/branding/alpha/wizHeaderRTL.bmp b/browser/branding/alpha/wizHeaderRTL.bmp new file mode 100644 index 0000000000000..c944205be23fd Binary files /dev/null and b/browser/branding/alpha/wizHeaderRTL.bmp differ diff --git a/browser/branding/alpha/wizWatermark.bmp b/browser/branding/alpha/wizWatermark.bmp new file mode 100644 index 0000000000000..9e523b5fa1966 Binary files /dev/null and b/browser/branding/alpha/wizWatermark.bmp differ diff --git a/browser/branding/branding-common.mozbuild b/browser/branding/branding-common.mozbuild index 908553b8b95c6..95cebf7359203 100644 --- a/browser/branding/branding-common.mozbuild +++ b/browser/branding/branding-common.mozbuild @@ -27,7 +27,9 @@ def FirefoxBranding(): FINAL_TARGET_FILES.chrome.icons.default += [ 'default128.png', 'default16.png', + 'default256.png', 'default32.png', 'default48.png', + 'default512.png', 'default64.png', ] diff --git a/browser/branding/nightly/VisualElements_150.png b/browser/branding/nightly/VisualElements_150.png index fa21911461749..a29d863d17668 100644 Binary files a/browser/branding/nightly/VisualElements_150.png and b/browser/branding/nightly/VisualElements_150.png differ diff --git a/browser/branding/nightly/VisualElements_70.png b/browser/branding/nightly/VisualElements_70.png index cefb95b1c3d2a..ccd90b8cf7484 100644 Binary files a/browser/branding/nightly/VisualElements_70.png and b/browser/branding/nightly/VisualElements_70.png differ diff --git a/browser/branding/nightly/configure.sh b/browser/branding/nightly/configure.sh index f51ece572b275..243091484f758 100644 --- a/browser/branding/nightly/configure.sh +++ b/browser/branding/nightly/configure.sh @@ -2,10 +2,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-MOZ_APP_DISPLAYNAME="Firefox Nightly" -MOZ_MACBUNDLE_ID=nightly - -MOZ_HANDLER_CLSID="4629216b-8753-41bf-9527-5bff51401671" -MOZ_IHANDLERCONTROL_IID="c57343fc-e011-40c2-b748-da82eabf0f1f" -MOZ_ASYNCIHANDLERCONTROL_IID="648c92a1-ea35-46da-a806-6b55c6247373" -MOZ_IGECKOBACKCHANNEL_IID="e61e038d-40dd-464a-9aba-66b206b6911b" +MOZ_APP_DISPLAYNAME="Tor Browser" diff --git a/browser/branding/nightly/content/identity-icons-brand.svg b/browser/branding/nightly/content/identity-icons-brand.svg new file mode 100644 index 0000000000000..382a061774aaa --- /dev/null +++ b/browser/branding/nightly/content/identity-icons-brand.svg @@ -0,0 +1,8 @@ +<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <g clip-rule="evenodd" fill-rule="evenodd"> + <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/> + <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/> + <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/> + </g> + <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/> +</svg> \ No newline at end of file diff --git a/browser/branding/nightly/content/jar.mn b/browser/branding/nightly/content/jar.mn index 6280b54882558..93ff6ecf736b3 100644 --- a/browser/branding/nightly/content/jar.mn +++ b/browser/branding/nightly/content/jar.mn @@ -16,4 +16,8 @@ browser.jar: content/branding/icon48.png (../default48.png) content/branding/icon64.png (../default64.png) content/branding/icon128.png (../default128.png) + content/branding/icon256.png (../default256.png) + content/branding/icon512.png (../default512.png) + content/branding/identity-icons-brand.svg content/branding/aboutDialog.css +* content/branding/tor-styles.css diff --git a/browser/branding/nightly/content/tor-styles.css b/browser/branding/nightly/content/tor-styles.css new file mode 100644 index 0000000000000..52e1761e54598 --- /dev/null +++ b/browser/branding/nightly/content/tor-styles.css @@ -0,0 +1,13 @@ +%include ../../tor-styles.inc.css + +/* default theme*/ +:root, +/* light theme*/ +:root:-moz-lwtheme-darktext { + --tor-branding-color: var(--blue-60); +} + +/* dark theme */ +:root:-moz-lwtheme-brighttext { + --tor-branding-color: var(--blue-40); +} \ No newline at end of file diff --git a/browser/branding/nightly/default128.png b/browser/branding/nightly/default128.png index 8fe085c56ffc3..12998ed018a75 100644 Binary files a/browser/branding/nightly/default128.png and b/browser/branding/nightly/default128.png differ diff --git a/browser/branding/nightly/default16.png b/browser/branding/nightly/default16.png index e01114ba2bb54..737ade977a6ba 100644 Binary files a/browser/branding/nightly/default16.png and b/browser/branding/nightly/default16.png differ diff --git a/browser/branding/nightly/default22.png b/browser/branding/nightly/default22.png index 0527dfd563cb2..02c87a9e2db67 100644 Binary files a/browser/branding/nightly/default22.png and b/browser/branding/nightly/default22.png differ diff --git a/browser/branding/nightly/default24.png b/browser/branding/nightly/default24.png index 019d020fde051..34cfedb2d908e 100644 Binary files a/browser/branding/nightly/default24.png and b/browser/branding/nightly/default24.png differ diff --git a/browser/branding/nightly/default256.png b/browser/branding/nightly/default256.png index d0d8bd01cc1aa..f619aecbc6e37 100644 Binary files a/browser/branding/nightly/default256.png and b/browser/branding/nightly/default256.png differ diff --git a/browser/branding/nightly/default32.png b/browser/branding/nightly/default32.png index c0986eae93674..499bc8ff7fc95 100644 Binary files a/browser/branding/nightly/default32.png and b/browser/branding/nightly/default32.png differ diff --git a/browser/branding/nightly/default48.png b/browser/branding/nightly/default48.png index 1980ffb35c805..fc99e3829d5f7 100644 Binary files a/browser/branding/nightly/default48.png and b/browser/branding/nightly/default48.png differ diff --git a/browser/branding/nightly/default512.png b/browser/branding/nightly/default512.png new file mode 100644 index 0000000000000..4ff5f7fa3495b Binary files /dev/null and b/browser/branding/nightly/default512.png differ diff --git a/browser/branding/nightly/default64.png b/browser/branding/nightly/default64.png index 551c98d44431c..5a84a53849420 100644 Binary files a/browser/branding/nightly/default64.png and b/browser/branding/nightly/default64.png differ diff --git a/browser/branding/nightly/document.icns b/browser/branding/nightly/document.icns index 8cb0f7f9dc32c..4acf7a5d1a4b3 100644 Binary files a/browser/branding/nightly/document.icns and b/browser/branding/nightly/document.icns differ diff --git a/browser/branding/nightly/document.ico b/browser/branding/nightly/document.ico index e5d0d840a7b42..ecb8e3dc6c731 100644 Binary files a/browser/branding/nightly/document.ico and b/browser/branding/nightly/document.ico differ diff --git a/browser/branding/nightly/firefox.VisualElementsManifest.xml b/browser/branding/nightly/firefox.VisualElementsManifest.xml index 85e09dd7a910f..a71938708affa 100644 --- a/browser/branding/nightly/firefox.VisualElementsManifest.xml +++ b/browser/branding/nightly/firefox.VisualElementsManifest.xml @@ -8,5 +8,5 @@ Square150x150Logo='browser\VisualElements\VisualElements_150.png' Square70x70Logo='browser\VisualElements\VisualElements_70.png' ForegroundText='light' - BackgroundColor='#20123a'/> + BackgroundColor='#1c191d'/> </Application> diff --git a/browser/branding/nightly/firefox.icns b/browser/branding/nightly/firefox.icns index 643ddd4f58125..4b0adc0f5af71 100644 Binary files a/browser/branding/nightly/firefox.icns and b/browser/branding/nightly/firefox.icns differ diff --git a/browser/branding/nightly/firefox.ico b/browser/branding/nightly/firefox.ico index 240b64298f763..eb28c93ab25f3 100644 Binary files a/browser/branding/nightly/firefox.ico and b/browser/branding/nightly/firefox.ico differ diff --git a/browser/branding/nightly/firefox.svg b/browser/branding/nightly/firefox.svg new file mode 100644 index 0000000000000..c11b568b8553d --- /dev/null +++ b/browser/branding/nightly/firefox.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="25.1281738%" y1="5.44281006%" x2="54.3792725%" y2="100%" id="linearGradient-1"> + <stop stop-color="#00E1E8" offset="0%"></stop> + <stop stop-color="#3500FF" offset="100%"></stop> + </linearGradient> + <linearGradient x1="25.1281738%" y1="5.44281006%" x2="54.3792725%" y2="100%" id="linearGradient-2"> + <stop stop-color="#00E1E8" offset="0%"></stop> + <stop stop-color="#3500FF" offset="100%"></stop> + </linearGradient> + <path d="M25,25 C152.50841,25 255.874399,127.979815 255.874399,255.011855 C255.874399,382.043895 152.50841,485.02371 25,485.02371 L25,25 Z" id="path-3"></path> + <filter x="-20.8%" y="-8.7%" width="134.7%" height="117.4%" filterUnits="objectBoundingBox" id="filter-4"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="12" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.0872579578 0 0 0 0 0.00490370801 0 0 0 0 0.234933036 0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Nightly" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g> + <circle id="background" fill-opacity="0.9" fill="#030004" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256 [...] + <g id="half" transform="translate(140.437200, 255.011855) scale(-1, 1) translate(-140.437200, -255.011855) "> + <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use> + <use fill="url(#linearGradient-2)" fill-rule="evenodd" xlink:href="#path-3"></use> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/browser/branding/nightly/firefox64.ico b/browser/branding/nightly/firefox64.ico index 1f50606af6a10..eb28c93ab25f3 100644 Binary files a/browser/branding/nightly/firefox64.ico and b/browser/branding/nightly/firefox64.ico differ diff --git a/browser/branding/nightly/locales/en-US/brand.dtd b/browser/branding/nightly/locales/en-US/brand.dtd index c56df31338b97..0b15c9978e017 100644 --- a/browser/branding/nightly/locales/en-US/brand.dtd +++ b/browser/branding/nightly/locales/en-US/brand.dtd @@ -2,10 +2,10 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<!ENTITY brandShorterName "Nightly"> -<!ENTITY brandShortName "Nightly"> -<!ENTITY brandFullName "Firefox Nightly"> +<!ENTITY brandShorterName "Tor Browser"> +<!ENTITY brandShortName "Tor Browser"> +<!ENTITY brandFullName "Tor Browser"> <!-- LOCALIZATION NOTE (brandProductName): This brand name can be used in messages where the product name needs to remain unchanged across different versions (Nightly, Beta, etc.). --> -<!ENTITY brandProductName "Firefox"> +<!ENTITY brandProductName "Tor Browser"> diff --git a/browser/branding/nightly/locales/en-US/brand.ftl b/browser/branding/nightly/locales/en-US/brand.ftl index f633bc269f58a..e970d32cb43e4 100644 --- a/browser/branding/nightly/locales/en-US/brand.ftl +++ b/browser/branding/nightly/locales/en-US/brand.ftl @@ -23,4 +23,4 @@ # remain unchanged across different versions (Nightly, Beta, etc.). -brand-product-name = Firefox -vendor-short-name = Mozilla -trademarkInfo = { " " } +trademarkInfo = Firefox and the Firefox logos are trademarks of the Mozilla Foundation. diff --git a/browser/branding/nightly/locales/en-US/brand.properties b/browser/branding/nightly/locales/en-US/brand.properties index de22973e0dfd6..3255b73d196a5 100644 --- a/browser/branding/nightly/locales/en-US/brand.properties +++ b/browser/branding/nightly/locales/en-US/brand.properties @@ -2,11 +2,11 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-brandShorterName=Nightly -brandShortName=Nightly -brandFullName=Firefox Nightly +brandShorterName=Tor Browser +brandShortName=Tor Browser +brandFullName=Tor Browser # LOCALIZATION NOTE(brandProductName): # This brand name can be used in messages where the product name needs to # remain unchanged across different versions (Nightly, Beta, etc.). -brandProductName=Firefox -vendorShortName=Mozilla +brandProductName=Tor Browser +vendorShortName=Tor Project diff --git a/browser/branding/nightly/locales/jar.mn b/browser/branding/nightly/locales/jar.mn index c04a7a1cf0f0a..d13c2110148f4 100644 --- a/browser/branding/nightly/locales/jar.mn +++ b/browser/branding/nightly/locales/jar.mn @@ -4,10 +4,9 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
[localization] @AB_CD@.jar: - branding (en-US/**/*.ftl) + branding (%*.ftl)
@AB_CD@.jar: % locale branding @AB_CD@ %locale/branding/ -# Nightly branding only exists in en-US - locale/branding/brand.dtd (en-US/brand.dtd) - locale/branding/brand.properties (en-US/brand.properties) + locale/branding/brand.dtd (%brand.dtd) + locale/branding/brand.properties (%brand.properties) diff --git a/browser/branding/nightly/locales/moz.build b/browser/branding/nightly/locales/moz.build index fff7035065b0a..d988c0ff9b162 100644 --- a/browser/branding/nightly/locales/moz.build +++ b/browser/branding/nightly/locales/moz.build @@ -4,6 +4,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DEFINES["MOZ_DISTRIBUTION_ID_UNQUOTED"] = CONFIG["MOZ_DISTRIBUTION_ID"] - JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/branding/nightly/wizHeader.bmp b/browser/branding/nightly/wizHeader.bmp index 89eaf901254c6..a754d2db1e114 100644 Binary files a/browser/branding/nightly/wizHeader.bmp and b/browser/branding/nightly/wizHeader.bmp differ diff --git a/browser/branding/nightly/wizHeaderRTL.bmp b/browser/branding/nightly/wizHeaderRTL.bmp index 451d87c70ef07..c944205be23fd 100644 Binary files a/browser/branding/nightly/wizHeaderRTL.bmp and b/browser/branding/nightly/wizHeaderRTL.bmp differ diff --git a/browser/branding/nightly/wizWatermark.bmp b/browser/branding/nightly/wizWatermark.bmp index f9d6a870e952c..9e523b5fa1966 100644 Binary files a/browser/branding/nightly/wizWatermark.bmp and b/browser/branding/nightly/wizWatermark.bmp differ diff --git a/browser/branding/official/VisualElements_150.png b/browser/branding/official/VisualElements_150.png index f764a48966cae..acc02c97d8273 100644 Binary files a/browser/branding/official/VisualElements_150.png and b/browser/branding/official/VisualElements_150.png differ diff --git a/browser/branding/official/VisualElements_70.png b/browser/branding/official/VisualElements_70.png index 197a645b4236e..890a227e251a1 100644 Binary files a/browser/branding/official/VisualElements_70.png and b/browser/branding/official/VisualElements_70.png differ diff --git a/browser/branding/official/configure.sh b/browser/branding/official/configure.sh index 1fbe981c9c5ac..243091484f758 100644 --- a/browser/branding/official/configure.sh +++ b/browser/branding/official/configure.sh @@ -2,18 +2,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-MOZ_APP_DISPLAYNAME=Firefox - -if test "$MOZ_UPDATE_CHANNEL" = "beta"; then - # Official beta builds - MOZ_HANDLER_CLSID="21e9f98d-a6c9-4cb5-b288-ae2fd2a96c58" - MOZ_IHANDLERCONTROL_IID="119149fa-d212-4f22-9517-082eecc1a084" - MOZ_ASYNCIHANDLERCONTROL_IID="4e253d9b-59cf-4b32-a973-38bc85495d61" - MOZ_IGECKOBACKCHANNEL_IID="77b75c7d-d1c2-4469-864d-31aaebb67cc6" -else - # Official release/esr builds - MOZ_HANDLER_CLSID="1baa303d-b4b9-45e5-9ccb-e3fca3e274b6" - MOZ_IHANDLERCONTROL_IID="ce30f77e-8847-44f0-a648-a9656bd89c0d" - MOZ_ASYNCIHANDLERCONTROL_IID="dca8d857-1a63-4045-8f36-8809eb093d04" - MOZ_IGECKOBACKCHANNEL_IID="b32983ff-ef84-4945-8f86-fb7491b4f57b" -fi +MOZ_APP_DISPLAYNAME="Tor Browser" diff --git a/browser/branding/official/content/identity-icons-brand.svg b/browser/branding/official/content/identity-icons-brand.svg new file mode 100644 index 0000000000000..382a061774aaa --- /dev/null +++ b/browser/branding/official/content/identity-icons-brand.svg @@ -0,0 +1,8 @@ +<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <g clip-rule="evenodd" fill-rule="evenodd"> + <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/> + <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/> + <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/> + </g> + <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/> +</svg> \ No newline at end of file diff --git a/browser/branding/official/content/jar.mn b/browser/branding/official/content/jar.mn index 6280b54882558..93ff6ecf736b3 100644 --- a/browser/branding/official/content/jar.mn +++ b/browser/branding/official/content/jar.mn @@ -16,4 +16,8 @@ browser.jar: content/branding/icon48.png (../default48.png) content/branding/icon64.png (../default64.png) content/branding/icon128.png (../default128.png) + content/branding/icon256.png (../default256.png) + content/branding/icon512.png (../default512.png) + content/branding/identity-icons-brand.svg content/branding/aboutDialog.css +* content/branding/tor-styles.css diff --git a/browser/branding/official/content/tor-styles.css b/browser/branding/official/content/tor-styles.css new file mode 100644 index 0000000000000..e4ccb5c767a90 --- /dev/null +++ b/browser/branding/official/content/tor-styles.css @@ -0,0 +1,14 @@ +%include ../../tor-styles.inc.css + +/* default theme*/ +:root, +/* light theme*/ +:root:-moz-lwtheme-darktext { + --tor-branding-color: var(--purple-60); +} + +/* dark theme */ +:root:-moz-lwtheme-brighttext { + --tor-branding-color: var(--purple-30); +} + diff --git a/browser/branding/official/default128.png b/browser/branding/official/default128.png index b92d78ca6d09c..18f3572d0d797 100644 Binary files a/browser/branding/official/default128.png and b/browser/branding/official/default128.png differ diff --git a/browser/branding/official/default16.png b/browser/branding/official/default16.png index fe860e46b1e7f..904b84e49871d 100644 Binary files a/browser/branding/official/default16.png and b/browser/branding/official/default16.png differ diff --git a/browser/branding/official/default22.png b/browser/branding/official/default22.png index 3aff987a84760..41cc3543d39fb 100644 Binary files a/browser/branding/official/default22.png and b/browser/branding/official/default22.png differ diff --git a/browser/branding/official/default24.png b/browser/branding/official/default24.png index cfce6e7d64fde..195cae90d3ed9 100644 Binary files a/browser/branding/official/default24.png and b/browser/branding/official/default24.png differ diff --git a/browser/branding/official/default256.png b/browser/branding/official/default256.png index ddc9d4db1f14a..809dbad4ab16b 100644 Binary files a/browser/branding/official/default256.png and b/browser/branding/official/default256.png differ diff --git a/browser/branding/official/default32.png b/browser/branding/official/default32.png index 67042dbb2b4a1..e8e68eb4492cb 100644 Binary files a/browser/branding/official/default32.png and b/browser/branding/official/default32.png differ diff --git a/browser/branding/official/default48.png b/browser/branding/official/default48.png index 765ea42459d31..e839211d260b4 100644 Binary files a/browser/branding/official/default48.png and b/browser/branding/official/default48.png differ diff --git a/browser/branding/official/default512.png b/browser/branding/official/default512.png new file mode 100644 index 0000000000000..23942859673d1 Binary files /dev/null and b/browser/branding/official/default512.png differ diff --git a/browser/branding/official/default64.png b/browser/branding/official/default64.png index 39e77389022cb..147a229fab8be 100644 Binary files a/browser/branding/official/default64.png and b/browser/branding/official/default64.png differ diff --git a/browser/branding/official/disk.icns b/browser/branding/official/disk.icns index 4353ef0965f34..3e2c44f187ce2 100644 Binary files a/browser/branding/official/disk.icns and b/browser/branding/official/disk.icns differ diff --git a/browser/branding/official/document.icns b/browser/branding/official/document.icns index 50d9701405a52..27a776a125577 100644 Binary files a/browser/branding/official/document.icns and b/browser/branding/official/document.icns differ diff --git a/browser/branding/official/document.ico b/browser/branding/official/document.ico index fcec7dc156465..3e5d99012f89e 100644 Binary files a/browser/branding/official/document.ico and b/browser/branding/official/document.ico differ diff --git a/browser/branding/official/firefox.VisualElementsManifest.xml b/browser/branding/official/firefox.VisualElementsManifest.xml index 85e09dd7a910f..3b2f265df6441 100644 --- a/browser/branding/official/firefox.VisualElementsManifest.xml +++ b/browser/branding/official/firefox.VisualElementsManifest.xml @@ -8,5 +8,5 @@ Square150x150Logo='browser\VisualElements\VisualElements_150.png' Square70x70Logo='browser\VisualElements\VisualElements_70.png' ForegroundText='light' - BackgroundColor='#20123a'/> + BackgroundColor='#420c5e'/> </Application> diff --git a/browser/branding/official/firefox.icns b/browser/branding/official/firefox.icns index 3cc884734c9d4..b9874461e519c 100644 Binary files a/browser/branding/official/firefox.icns and b/browser/branding/official/firefox.icns differ diff --git a/browser/branding/official/firefox.ico b/browser/branding/official/firefox.ico index d8ba663ba76e9..db0a9af865b67 100644 Binary files a/browser/branding/official/firefox.ico and b/browser/branding/official/firefox.ico differ diff --git a/browser/branding/official/firefox.svg b/browser/branding/official/firefox.svg new file mode 100644 index 0000000000000..9240dc6e84caa --- /dev/null +++ b/browser/branding/official/firefox.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> + <stop stop-color="#420C5D" offset="0%"></stop> + <stop stop-color="#951AD1" offset="100%"></stop> + </linearGradient> + <path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path> + <filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Assets" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="icon_512x512"> + <g id="Group"> + <g id="tb_icon/Stable"> + <g id="Stable"> + <circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,15 [...] + <g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) "> + <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use> + <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + </g> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/browser/branding/official/firefox64.ico b/browser/branding/official/firefox64.ico index c3a32449d27a9..db0a9af865b67 100644 Binary files a/browser/branding/official/firefox64.ico and b/browser/branding/official/firefox64.ico differ diff --git a/browser/branding/official/locales/en-US/brand.dtd b/browser/branding/official/locales/en-US/brand.dtd index d094ad0f8d018..0b15c9978e017 100644 --- a/browser/branding/official/locales/en-US/brand.dtd +++ b/browser/branding/official/locales/en-US/brand.dtd @@ -2,10 +2,10 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<!ENTITY brandShorterName "Firefox"> -<!ENTITY brandShortName "Firefox"> -<!ENTITY brandFullName "Mozilla Firefox"> +<!ENTITY brandShorterName "Tor Browser"> +<!ENTITY brandShortName "Tor Browser"> +<!ENTITY brandFullName "Tor Browser"> <!-- LOCALIZATION NOTE (brandProductName): This brand name can be used in messages where the product name needs to remain unchanged across different versions (Nightly, Beta, etc.). --> -<!ENTITY brandProductName "Firefox"> +<!ENTITY brandProductName "Tor Browser"> diff --git a/browser/branding/official/locales/en-US/brand.properties b/browser/branding/official/locales/en-US/brand.properties index 8ade3cb6e5bb9..3255b73d196a5 100644 --- a/browser/branding/official/locales/en-US/brand.properties +++ b/browser/branding/official/locales/en-US/brand.properties @@ -2,11 +2,11 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-brandShorterName=Firefox -brandShortName=Firefox -brandFullName=Mozilla Firefox +brandShorterName=Tor Browser +brandShortName=Tor Browser +brandFullName=Tor Browser # LOCALIZATION NOTE(brandProductName): # This brand name can be used in messages where the product name needs to # remain unchanged across different versions (Nightly, Beta, etc.). -brandProductName=Firefox -vendorShortName=Mozilla +brandProductName=Tor Browser +vendorShortName=Tor Project diff --git a/browser/branding/official/wizHeader.bmp b/browser/branding/official/wizHeader.bmp index 420824226dfe8..a754d2db1e114 100644 Binary files a/browser/branding/official/wizHeader.bmp and b/browser/branding/official/wizHeader.bmp differ diff --git a/browser/branding/official/wizHeaderRTL.bmp b/browser/branding/official/wizHeaderRTL.bmp index 7f74929910bdb..c944205be23fd 100644 Binary files a/browser/branding/official/wizHeaderRTL.bmp and b/browser/branding/official/wizHeaderRTL.bmp differ diff --git a/browser/branding/official/wizWatermark.bmp b/browser/branding/official/wizWatermark.bmp index b3b3c91d327c9..9e523b5fa1966 100644 Binary files a/browser/branding/official/wizWatermark.bmp and b/browser/branding/official/wizWatermark.bmp differ diff --git a/browser/branding/tor-styles.inc.css b/browser/branding/tor-styles.inc.css new file mode 100644 index 0000000000000..55dc9b6238b3a --- /dev/null +++ b/browser/branding/tor-styles.inc.css @@ -0,0 +1,87 @@ +:root { + /* photon colors, not all of them are available for whatever reason + in firefox, so here they are */ + + --magenta-50: #ff1ad9; + --magenta-60: #ed00b5; + --magenta-70: #b5007f; + --magenta-80: #7d004f; + --magenta-90: #440027; + + --purple-30: #c069ff; + --purple-40: #ad3bff; + --purple-50: #9400ff; + --purple-60: #8000d7; + --purple-70: #6200a4; + --purple-80: #440071; + --purple-90: #25003e; + + --blue-40: #45a1ff; + --blue-50: #0a84ff; + --blue-50-a30: rgba(10, 132, 255, 0.3); + --blue-60: #0060df; + --blue-70: #003eaa; + --blue-80: #002275; + --blue-90: #000f40; + + --teal-50: #00feff; + --teal-60: #00c8d7; + --teal-70: #008ea4; + --teal-80: #005a71; + --teal-90: #002d3e; + + --green-50: #30e60b; + --green-60: #12bc00; + --green-70: #058b00; + --green-80: #006504; + --green-90: #003706; + + --yellow-50: #ffe900; + --yellow-60: #d7b600; + --yellow-70: #a47f00; + --yellow-80: #715100; + --yellow-90: #3e2800; + + --red-50: #ff0039; + --red-60: #d70022; + --red-70: #a4000f; + --red-80: #5a0002; + --red-90: #3e0200; + + --orange-50: #ff9400; + --orange-60: #d76e00; + --orange-70: #a44900; + --orange-80: #712b00; + --orange-90: #3e1300; + + --grey-10: #f9f9fa; + --grey-10-a10: rgba(249, 249, 250, 0.1); + --grey-10-a20: rgba(249, 249, 250, 0.2); + --grey-10-a40: rgba(249, 249, 250, 0.4); + --grey-10-a60: rgba(249, 249, 250, 0.6); + --grey-10-a80: rgba(249, 249, 250, 0.8); + --grey-20: #ededf0; + --grey-30: #d7d7db; + --grey-40: #b1b1b3; + --grey-50: #737373; + --grey-60: #4a4a4f; + --grey-70: #38383d; + --grey-80: #2a2a2e; + --grey-90: #0c0c0d; + --grey-90-a05: rgba(12, 12, 13, 0.05); + --grey-90-a10: rgba(12, 12, 13, 0.1); + --grey-90-a20: rgba(12, 12, 13, 0.2); + --grey-90-a30: rgba(12, 12, 13, 0.3); + --grey-90-a40: rgba(12, 12, 13, 0.4); + --grey-90-a50: rgba(12, 12, 13, 0.5); + --grey-90-a60: rgba(12, 12, 13, 0.6); + --grey-90-a70: rgba(12, 12, 13, 0.7); + --grey-90-a80: rgba(12, 12, 13, 0.8); + --grey-90-a90: rgba(12, 12, 13, 0.9); + + --ink-70: #363959; + --ink-80: #202340; + --ink-90: #0f1126; + + --white-100: #ffffff; +} \ No newline at end of file diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js index 91e9e469cea26..a3656f827ffc6 100644 --- a/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js @@ -157,10 +157,7 @@ function init_all() {
gotoPref().then(() => { let helpButton = document.getElementById("helpButton"); - let helpUrl = - Services.urlFormatter.formatURLPref("app.support.baseURL") + - "preferences"; - helpButton.setAttribute("href", helpUrl); + helpButton.setAttribute("href", "https://support.torproject.org/tbb");
document.getElementById("addonsButton").addEventListener("click", e => { if (e.button >= 2) { diff --git a/browser/themes/shared/controlcenter/panel.inc.css b/browser/themes/shared/controlcenter/panel.inc.css index 67e265d063933..2755b70b55423 100644 --- a/browser/themes/shared/controlcenter/panel.inc.css +++ b/browser/themes/shared/controlcenter/panel.inc.css @@ -13,7 +13,7 @@ #identity-popup, #permission-popup, #protections-popup { - --popup-width: 30.81em; + --popup-width: 40em; /* Set default fill for icons in the identity popup. Individual icons can override this. */ fill: currentColor; @@ -363,7 +363,7 @@
.identity-popup-security-connection.identity-button { padding: 0; - width: 28em; + width: 37.5em; background-position-x: 4px; margin-inline-end: -6px; } diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css index ee60b2ff5f319..a01e7d705cd72 100644 --- a/browser/themes/shared/identity-block/identity-block.inc.css +++ b/browser/themes/shared/identity-block/identity-block.inc.css @@ -54,6 +54,11 @@ }
#identity-box[pageproxystate="valid"].notSecureText #identity-icon-label, +#identity-box[pageproxystate="valid"].chromeUI #identity-icon-label { + color: var(--tor-branding-color); + opacity: 1; +} + #identity-box[pageproxystate="valid"].chromeUI #identity-icon-label, #identity-box[pageproxystate="valid"].extensionPage #identity-icon-label, .urlbar-label { @@ -150,12 +155,9 @@ }
#identity-box[pageproxystate="valid"].chromeUI #identity-icon { - list-style-image: url(chrome://branding/content/icon16.png); -} -@media (min-resolution: 1.1dppx) { - #identity-box[pageproxystate="valid"].chromeUI #identity-icon { - list-style-image: url(chrome://branding/content/icon32.png); - } + list-style-image: url(chrome://branding/content/identity-icons-brand.svg); + fill: var(--tor-branding-color); + fill-opacity: 1; }
#identity-box[pageproxystate="valid"].localResource #identity-icon { diff --git a/browser/themes/shared/identity-block/onion-slash.svg b/browser/themes/shared/identity-block/onion-slash.svg new file mode 100644 index 0000000000000..93eb24b039055 --- /dev/null +++ b/browser/themes/shared/identity-block/onion-slash.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="m14.1161 15.6245c-.0821.0001-.1634-.016-.2393-.0474-.0758-.0314-.1447-.0775-.2027-.1356l-12.749984-12.749c-.109266-.11882-.168406-.27526-.165071-.43666.003335-.16139.068886-.31525.182967-.42946.114078-.11421.267868-.17994.429258-.18345.16139-.00352.3179.05544.43685.16457l12.74998 12.75c.1168.1176.1824.2767.1824.4425s-.0656.3249-.1824.4425c-.058.058-.1269.1039-.2028.1352-.0759.0312-.1571.0471-.2392.0468z" fill-opacity="context-fill-opacity" fill="#ff0039" /> + <path d="m 8,0.5000002 c -1.61963,0 -3.1197431,0.5137987 -4.3457031,1.3867188 l 0.84375,0.8417968 0.7792969,0.78125 0.8613281,0.8613282 0.8164062,0.8164062 0.9863281,0.984375 h 0.058594 c 1.00965,0 1.828125,0.818485 1.828125,1.828125 0,0.01968 6.2e-4,0.039074 0,0.058594 L 10.8125,9.0449221 C 10.9334,8.7195921 11,8.3674002 11,8.0000002 c 0,-1.65685 -1.34314,-3 -3,-3 v -1.078125 c 2.25231,0 4.078125,1.825845 4.078125,4.078125 0,0.67051 -0.162519,1.3033281 -0.449219,1.8613281 l 0.861328,0 [...] +</svg> diff --git a/browser/themes/shared/identity-block/onion-warning.svg b/browser/themes/shared/identity-block/onion-warning.svg new file mode 100644 index 0000000000000..f920a93ac4105 --- /dev/null +++ b/browser/themes/shared/identity-block/onion-warning.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="M 7.5,0.5 C 3.35786,0.5 0,3.85786 0,8 c 0,3.7093 2.6930488,6.789278 6.2304688,7.392578 -0.032181,-0.0637 -0.060149,-0.128686 -0.085938,-0.195312 -0.00862,-0.02227 -0.01751,-0.04385 -0.025391,-0.06641 -0.023385,-0.0669 -0.043878,-0.135932 -0.060547,-0.205078 -0.00186,-0.0077 -0.00213,-0.01571 -0.00391,-0.02344 -0.017615,-0.07685 -0.032109,-0.153488 -0.041016,-0.232422 -7.27e-5,-6.44e-4 7.2e-5,-0.0013 0,-0.002 -0.0087,-0.07777 -0.011896,-0.157155 -0.011719,-0.236328 7.71e-5,-0.0 [...] +</svg> diff --git a/browser/themes/shared/identity-block/onion.svg b/browser/themes/shared/identity-block/onion.svg new file mode 100644 index 0000000000000..7655a800d9eec --- /dev/null +++ b/browser/themes/shared/identity-block/onion.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="M 8 0.5 C 3.85786 0.5 0.5 3.85786 0.5 8 C 0.5 12.1421 3.85786 15.5 8 15.5 C 12.1421 15.5 15.5 12.1421 15.5 8 C 15.5 3.85786 12.1421 0.5 8 0.5 z M 8 1.671875 C 11.4949 1.671875 14.328125 4.50507 14.328125 8 C 14.328125 11.4949 11.4949 14.328125 8 14.328125 L 8 13.25 C 10.89951 13.25 13.25 10.89951 13.25 8 C 13.25 5.10051 10.89951 2.75 8 2.75 L 8 1.671875 z M 8 3.921875 C 10.25231 3.921875 12.078125 5.74772 12.078125 8 C 12.078125 10.25231 10.25231 12.078125 8 12.078125 L 8 11 C [...] +</svg> diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn index 4f74932df96fb..f76184171ddd7 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -9,6 +9,8 @@
skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css) skin/classic/browser/offlineSupportPages.css (../shared/offlineSupportPages.css) + skin/classic/browser/onionPattern.css (../shared/onionPattern.css) + skin/classic/browser/onionPattern.svg (../shared/onionPattern.svg) skin/classic/browser/blockedSite.css (../shared/blockedSite.css) skin/classic/browser/error-pages.css (../shared/error-pages.css) skin/classic/browser/aboutRestartRequired.css (../shared/aboutRestartRequired.css) @@ -49,6 +51,9 @@ skin/classic/browser/downloads/notification-start-animation.svg (../shared/downloads/notification-start-animation.svg) skin/classic/browser/drm-icon.svg (../shared/drm-icon.svg) skin/classic/browser/permissions.svg (../shared/identity-block/permissions.svg) + skin/classic/browser/onion.svg (../shared/identity-block/onion.svg) + skin/classic/browser/onion-slash.svg (../shared/identity-block/onion-slash.svg) + skin/classic/browser/onion-warning.svg (../shared/identity-block/onion-warning.svg) skin/classic/browser/illustrations/error-malformed-url.svg (../shared/illustrations/error-malformed-url.svg) skin/classic/browser/notification-icons/autoplay-media.svg (../shared/notification-icons/autoplay-media.svg) skin/classic/browser/notification-icons/autoplay-media-blocked.svg (../shared/notification-icons/autoplay-media-blocked.svg) diff --git a/browser/themes/shared/onionPattern.css b/browser/themes/shared/onionPattern.css new file mode 100644 index 0000000000000..1852350d57f73 --- /dev/null +++ b/browser/themes/shared/onionPattern.css @@ -0,0 +1,31 @@ +/* Onion pattern */ + +.onion-pattern-container { + + flex: auto; /* grow to consume remaining space on the page */ + display: flex; + margin: 0 auto; + width: 100%; + /* two onions tall, 4x the radius */ + height: calc(4 * var(--onion-radius)); + max-height: calc(4 * var(--onion-radius)); + min-height: calc(4 * var(--onion-radius)); + direction: ltr; +} + +.onion-pattern-crop { + height: 100%; + width: 100%; + + -moz-context-properties: fill; + fill: var(--onion-color, currentColor); + /* opacity of the entire div, not context-opacity */ + opacity: var(--onion-opacity, 1); + + background-image: url("chrome://browser/skin/onionPattern.svg"); + background-repeat: repeat; + background-attachment: local; + background-position: center; + /* svg source is 6 onions wide and 2 onions tall */ + background-size: calc(6 * 2 * var(--onion-radius)) calc(2 * 2 * var(--onion-radius));; +} \ No newline at end of file diff --git a/browser/themes/shared/onionPattern.inc.xhtml b/browser/themes/shared/onionPattern.inc.xhtml new file mode 100644 index 0000000000000..de57b6ee301a3 --- /dev/null +++ b/browser/themes/shared/onionPattern.inc.xhtml @@ -0,0 +1,12 @@ +<!-- + Container div that holds onionPattern.svg + It is expected the includer of this xhtml file also includes onionPattern.css + and define the following vars: + onion-radius : radius of an onion + onion-color : the base color of the onion pattern + onion-opacity : the opacity of the entire repeating pattern +--> + +<div class="onion-pattern-container"> + <div class="onion-pattern-crop"/> +</div> \ No newline at end of file diff --git a/browser/themes/shared/onionPattern.svg b/browser/themes/shared/onionPattern.svg new file mode 100644 index 0000000000000..e2937b1753414 --- /dev/null +++ b/browser/themes/shared/onionPattern.svg @@ -0,0 +1,22 @@ +<svg fill="context-fill" viewBox="0 0 900 300" width="900" height="300" xmlns="http://www.w3.org/2000/svg"> + <g> + <path d="m825 0c41.421 0 75 33.5786 75 75 0 41.421-33.579 75-75 75z" fill-opacity=".3"/> + <path d="m750 0c41.421 0 75 33.5786 75 75 0 41.421-33.579 75-75 75z" fill-opacity=".15"/> + <path d="m525 225c0-41.421-33.579-75-75-75s-75 33.579-75 75z" fill-opacity=".3"/> + <path d="m525 300c0-41.421-33.579-75-75-75s-75 33.579-75 75z" fill-opacity=".15"/> + <path d="m300 0c0 41.4214-33.579 75-75 75s-75-33.5786-75-75z" fill-opacity=".3"/> + <path d="m300 75c0 41.421-33.579 75-75 75s-75-33.579-75-75z" fill-opacity=".15"/> + <g clip-rule="evenodd" fill-opacity=".3" fill-rule="evenodd"> + <path d="m525 .25c-.176 0-.351.000606-.527.001817-.966.006671-1.744.795563-1.737 1.762033.006.96648.795 1.74455 1.762 1.73788.167-.00115.334-.00173.502-.00173s.335.00058.502.00173c.967.00667 1.756-.7714 1.762-1.73788.007-.96647-.771-1.755363-1.737-1.762033-.176-.001211-.351-.001817-.527-.001817zm7.849.407251c-.962-.100329-1.822.597609-1.923 1.558879-.1.96128.598 1.82188 1.559 1.92221.333.03473.665.07174.996.11103.96.11381 1.83-.57199 1.944-1.53176s-.572-1.830084-1.532-1.94389 [...] + <path d="m3.75 75c0-39.3503 31.8997-71.25 71.25-71.25 39.35 0 71.25 31.8997 71.25 71.25 0 39.35-31.9 71.25-71.25 71.25-39.3503 0-71.25-31.9-71.25-71.25zm71.25-74.75c-41.2833 0-74.75 33.4667-74.75 74.75 0 41.283 33.4667 74.75 74.75 74.75 41.283 0 74.75-33.467 74.75-74.75 0-41.2833-33.467-74.75-74.75-74.75zm-55.25 74.75c0-30.5137 24.7363-55.25 55.25-55.25 30.514 0 55.25 24.7363 55.25 55.25 0 30.514-24.736 55.25-55.25 55.25-30.5137 0-55.25-24.736-55.25-55.25zm55.25-58.75c-32.446 [...] + <path d="m228.75 225c0-39.35 31.9-71.25 71.25-71.25s71.25 31.9 71.25 71.25-31.9 71.25-71.25 71.25-71.25-31.9-71.25-71.25zm71.25-74.75c-41.283 0-74.75 33.467-74.75 74.75s33.467 74.75 74.75 74.75 74.75-33.467 74.75-74.75-33.467-74.75-74.75-74.75zm-55.25 74.75c0-30.514 24.736-55.25 55.25-55.25s55.25 24.736 55.25 55.25-24.736 55.25-55.25 55.25-55.25-24.736-55.25-55.25zm55.25-58.75c-32.447 0-58.75 26.303-58.75 58.75s26.303 58.75 58.75 58.75 58.75-26.303 58.75-58.75-26.303-58.75-58 [...] + <path d="m828.75 225c0-39.35 31.9-71.25 71.25-71.25v-3.5c-41.283 0-74.75 33.467-74.75 74.75s33.467 74.75 74.75 74.75v-3.5c-39.35 0-71.25-31.9-71.25-71.25zm16 0c0-30.514 24.736-55.25 55.25-55.25v-3.5c-32.447 0-58.75 26.303-58.75 58.75s26.303 58.75 58.75 58.75v-3.5c-30.514 0-55.25-24.736-55.25-55.25zm55.25-39.25c-21.677 0-39.25 17.573-39.25 39.25s17.573 39.25 39.25 39.25v3.5c-23.61 0-42.75-19.14-42.75-42.75s19.14-42.75 42.75-42.75zm-22.25 39.25c0-12.288 9.962-22.25 22.25-22.25v [...] + <path d="m71.25 225c0-39.35-31.8997-71.25-71.25-71.25v-3.5c41.2833 0 74.75 33.467 74.75 74.75s-33.4667 74.75-74.75 74.75v-3.5c39.3503 0 71.25-31.9 71.25-71.25zm-16 0c0-30.514-24.7363-55.25-55.25-55.25v-3.5c32.4467 0 58.75 26.303 58.75 58.75s-26.3033 58.75-58.75 58.75v-3.5c30.5137 0 55.25-24.736 55.25-55.25zm-55.25-39.25c21.6772 0 39.25 17.573 39.25 39.25s-17.5728 39.25-39.25 39.25v3.5c23.6102 0 42.75-19.14 42.75-42.75s-19.1398-42.75-42.75-42.75zm22.25 39.25c0-12.288-9.9617-22 [...] + <path d="m303.75 75c0-39.3503 31.9-71.25 71.25-71.25s71.25 31.8997 71.25 71.25c0 39.35-31.9 71.25-71.25 71.25s-71.25-31.9-71.25-71.25zm71.25-74.75c-41.283 0-74.75 33.4667-74.75 74.75 0 41.283 33.467 74.75 74.75 74.75s74.75-33.467 74.75-74.75c0-41.2833-33.467-74.75-74.75-74.75zm-55.25 74.75c0-30.5137 24.736-55.25 55.25-55.25s55.25 24.7363 55.25 55.25c0 30.514-24.736 55.25-55.25 55.25s-55.25-24.736-55.25-55.25zm55.25-58.75c-32.447 0-58.75 26.3033-58.75 58.75 0 32.447 26.303 58. [...] + <path d="m603.75 75c0-39.3503 31.9-71.25 71.25-71.25s71.25 31.8997 71.25 71.25c0 39.35-31.9 71.25-71.25 71.25s-71.25-31.9-71.25-71.25zm71.25-74.75c-41.283 0-74.75 33.4667-74.75 74.75 0 41.283 33.467 74.75 74.75 74.75s74.75-33.467 74.75-74.75c0-41.2833-33.467-74.75-74.75-74.75zm-55.25 74.75c0-30.5137 24.736-55.25 55.25-55.25s55.25 24.7363 55.25 55.25c0 30.514-24.736 55.25-55.25 55.25s-55.25-24.736-55.25-55.25zm55.25-58.75c-32.447 0-58.75 26.3033-58.75 58.75 0 32.447 26.303 58. [...] + <path d="m150 150.25c-.878 0-1.753.015-2.624.045-.966.034-1.722.844-1.689 1.81.033.965.843 1.721 1.809 1.688.831-.029 1.666-.043 2.504-.043s1.673.014 2.504.043c.966.033 1.776-.723 1.809-1.688.033-.966-.723-1.776-1.689-1.81-.871-.03-1.746-.045-2.624-.045zm-11.449 4.415c.954-.154 1.603-1.053 1.449-2.007s-1.053-1.603-2.007-1.449c-1.735.281-3.45.621-5.143 1.018-.941.221-1.525 1.163-1.304 2.104s1.163 1.524 2.104 1.303c1.613-.378 3.248-.702 4.901-.969zm23.456-3.456c-.954-.154-1.853 [...] + <path d="m750 150.25c-.878 0-1.753.015-2.624.045-.966.034-1.722.844-1.689 1.81.033.965.843 1.721 1.809 1.688.831-.029 1.666-.043 2.504-.043s1.673.014 2.504.043c.966.033 1.776-.723 1.809-1.688.033-.966-.723-1.776-1.689-1.81-.871-.03-1.746-.045-2.624-.045zm-11.449 4.415c.954-.154 1.603-1.053 1.449-2.007s-1.053-1.603-2.007-1.449c-1.735.281-3.45.621-5.143 1.018-.941.221-1.525 1.163-1.304 2.104s1.163 1.524 2.104 1.303c1.613-.378 3.248-.702 4.901-.969zm23.456-3.456c-.954-.154-1.853 [...] + <path d="m528.75 225c0-39.35 31.9-71.25 71.25-71.25s71.25 31.9 71.25 71.25-31.9 71.25-71.25 71.25-71.25-31.9-71.25-71.25zm71.25-74.75c-41.283 0-74.75 33.467-74.75 74.75s33.467 74.75 74.75 74.75 74.75-33.467 74.75-74.75-33.467-74.75-74.75-74.75zm-55.25 74.75c0-30.514 24.736-55.25 55.25-55.25s55.25 24.736 55.25 55.25-24.736 55.25-55.25 55.25-55.25-24.736-55.25-55.25zm55.25-58.75c-32.447 0-58.75 26.303-58.75 58.75s26.303 58.75 58.75 58.75 58.75-26.303 58.75-58.75-26.303-58.75-58 [...] + </g> + </g> +</svg> \ No newline at end of file diff --git a/devtools/client/themes/images/aboutdebugging-firefox-aurora.svg b/devtools/client/themes/images/aboutdebugging-firefox-aurora.svg index d4c0cdace9feb..9240dc6e84caa 100644 --- a/devtools/client/themes/images/aboutdebugging-firefox-aurora.svg +++ b/devtools/client/themes/images/aboutdebugging-firefox-aurora.svg @@ -1,4 +1,31 @@ -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><linearGradient x1="42%" y1="-10%" x2="61%" y2="114%" id="f"><stop stop-color="#AAF2FF" offset="0%"/><stop stop-color="#0DF" offset="29%"/><stop stop-color="#0090ED" offset="61%"/><stop stop-color="#0250BB" offset="89%"/></linearGradient><linearGradient x1="38%" y1="0%" x2="63%" y2="124%" id="g"><stop stop-color="#AAF2FF" offset="0%"/><stop stop-color="#0DF" offset="29%"/><stop stop-color="#0090ED" offset="74%"/><stop st [...] +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> + <stop stop-color="#420C5D" offset="0%"></stop> + <stop stop-color="#951AD1" offset="100%"></stop> + </linearGradient> + <path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path> + <filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Assets" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="icon_512x512"> + <g id="Group"> + <g id="tb_icon/Stable"> + <g id="Stable"> + <circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,15 [...] + <g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) "> + <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use> + <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + </g> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/devtools/client/themes/images/aboutdebugging-firefox-beta.svg b/devtools/client/themes/images/aboutdebugging-firefox-beta.svg index 8ece78c5c1cda..9240dc6e84caa 100644 --- a/devtools/client/themes/images/aboutdebugging-firefox-beta.svg +++ b/devtools/client/themes/images/aboutdebugging-firefox-beta.svg @@ -1,4 +1,31 @@ -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512"><defs><radialGradient id="c" cx="87.4%" cy="-12.9%" r="128%" gradientTransform="matrix(.8 0 0 1 .178 .129)"><stop offset=".13" stop-color="#ffbd4f"/><stop offset=".28" stop-color="#ff980e"/><stop offset=".47" stop-color="#ff3750"/><stop offset=".78" stop-color="#eb0878"/><stop offset=".86" stop-color="#e50080"/></radialGradient><radialGradient id="d" cx="49%" cy="40%" r="128%" gradientTransform="matrix(.82 0 0 1 .088 0)"><s [...] +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> + <stop stop-color="#420C5D" offset="0%"></stop> + <stop stop-color="#951AD1" offset="100%"></stop> + </linearGradient> + <path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path> + <filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Assets" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="icon_512x512"> + <g id="Group"> + <g id="tb_icon/Stable"> + <g id="Stable"> + <circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,15 [...] + <g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) "> + <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use> + <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + </g> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/devtools/client/themes/images/aboutdebugging-firefox-logo.svg b/devtools/client/themes/images/aboutdebugging-firefox-logo.svg index fe4d116b16609..d7895f1107c58 100644 --- a/devtools/client/themes/images/aboutdebugging-firefox-logo.svg +++ b/devtools/client/themes/images/aboutdebugging-firefox-logo.svg @@ -1,6 +1,5 @@ -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> - <path fill="context-fill #20123a" d="M190.368 150.591c0.157 0.009 0.079 0.003 0 0zm-57.874-28.933c0.158 0.008 0.079 0.003 0 0zm346.228 44.674c-10.445-25.123-31.6-52.248-48.211-60.82 13.52 26.5 21.345 53.093 24.335 72.935 0 0.04 0.015 0.136 0.047 0.4-27.175-67.732-73.254-95.047-110.886-154.512-1.9-3.008-3.805-6.022-5.661-9.2a73.237 73.237 0 0 1-2.646-4.972 43.757 43.757 0 0 1-3.585-9.5 0.625 0.625 0 0 0-0.546-0.644 0.8 0.8 0 0 0-0.451 0c-0.033 0.011-0.084 0.051-0.119 0.065-0.053 0.02-0. [...] -</svg> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <g fill="context-fill" fill-opacity="context-fill-opacity" fill-rule="nonzero"> + <path d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623 [...] + </g> +</svg> \ No newline at end of file diff --git a/devtools/client/themes/images/aboutdebugging-firefox-nightly.svg b/devtools/client/themes/images/aboutdebugging-firefox-nightly.svg index dbc7b084d6c06..9240dc6e84caa 100644 --- a/devtools/client/themes/images/aboutdebugging-firefox-nightly.svg +++ b/devtools/client/themes/images/aboutdebugging-firefox-nightly.svg @@ -1,4 +1,31 @@ -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><radialGradient id="b" cx="-9227.187" cy="-9815.121" r="80.797" gradientTransform="matrix(6.201 0 0 6.2 57644.994 60908.8)" gradientUnits="userSpaceOnUse"><stop offset=".108" stop-color="#3fe1b0"/><stop offset=".122" stop-color="#3bdcb3"/><stop offset=".254" stop-color="#1bb3d3"/><stop offset=".358" stop-color="#0799e6"/><stop offset=".42" stop-color="#0090ed"/><stop offset=".487" stop-color="#2482f1"/><stop offset=".64" [...] +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> + <stop stop-color="#420C5D" offset="0%"></stop> + <stop stop-color="#951AD1" offset="100%"></stop> + </linearGradient> + <path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path> + <filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Assets" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="icon_512x512"> + <g id="Group"> + <g id="tb_icon/Stable"> + <g id="Stable"> + <circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,15 [...] + <g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) "> + <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use> + <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + </g> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/devtools/client/themes/images/aboutdebugging-firefox-release.svg b/devtools/client/themes/images/aboutdebugging-firefox-release.svg index 4c195cf17c853..9240dc6e84caa 100644 --- a/devtools/client/themes/images/aboutdebugging-firefox-release.svg +++ b/devtools/client/themes/images/aboutdebugging-firefox-release.svg @@ -1,4 +1,31 @@ -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><radialGradient id="b" cx="87.4%" cy="-12.9%" r="128%" gradientTransform="matrix(.8 0 0 1 .178 .129)"><stop offset=".13" stop-color="#ffbd4f"/><stop offset=".28" stop-color="#ff980e"/><stop offset=".47" stop-color="#ff3750"/><stop offset=".78" stop-color="#eb0878"/><stop offset=".86" stop-color="#e50080"/></radialGradient><radialGradient id="c" cx="49%" cy="40%" r="128%" gradientTransform="matrix(.82 0 0 1 .088 0)"><stop [...] +<?xml version="1.0" encoding="UTF-8"?> +<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> + <stop stop-color="#420C5D" offset="0%"></stop> + <stop stop-color="#951AD1" offset="100%"></stop> + </linearGradient> + <path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path> + <filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3"> + <feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="Assets" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="icon_512x512"> + <g id="Group"> + <g id="tb_icon/Stable"> + <g id="Stable"> + <circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle> + <path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,15 [...] + <g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) "> + <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use> + <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + </g> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/intl/l10n/L10nRegistry.jsm b/intl/l10n/L10nRegistry.jsm index ca38f37839107..dded11836ab43 100644 --- a/intl/l10n/L10nRegistry.jsm +++ b/intl/l10n/L10nRegistry.jsm @@ -148,6 +148,12 @@ class L10nRegistryService { async* generateBundles(requestedLangs, resourceIds) { const resourceIdsDedup = Array.from(new Set(resourceIds)); const sourcesOrder = Array.from(this.sources.keys()).reverse(); + // Always prioritize torbutton sources (keep in sync with generateBundlesSync) + const idxTB = sourcesOrder.indexOf("torbutton"); + if (idxTB > 0) { + sourcesOrder.splice(idxTB, 1); + sourcesOrder.unshift("torbutton"); + } const pseudoStrategy = Services.prefs.getStringPref("intl.l10n.pseudo", ""); for (const locale of requestedLangs) { for await (const dataSets of generateResourceSetsForLocale(locale, sourcesOrder, resourceIdsDedup)) { @@ -181,6 +187,12 @@ class L10nRegistryService { * generateBundlesSync(requestedLangs, resourceIds) { const resourceIdsDedup = Array.from(new Set(resourceIds)); const sourcesOrder = Array.from(this.sources.keys()).reverse(); + // Always prioritize torbutton sources (keep in sync with generateBundles) + const idxTB = sourcesOrder.indexOf("torbutton"); + if (idxTB > 0) { + sourcesOrder.splice(idxTB, 1); + sourcesOrder.unshift("torbutton"); + } const pseudoStrategy = Services.prefs.getStringPref("intl.l10n.pseudo", ""); for (const locale of requestedLangs) { for (const dataSets of generateResourceSetsForLocaleSync(locale, sourcesOrder, resourceIdsDedup)) { @@ -327,7 +339,9 @@ class L10nRegistryService {
for (const source of this.sources.values()) { for (const locale of source.locales) { - locales.add(locale); + if (!source.skipForAvailableLocales) { + locales.add(locale); + } } } return Array.from(locales); @@ -529,10 +543,11 @@ class FileSource { * * @returns {FileSource} */ - constructor(name, locales, prePath) { + constructor(name, locales, prePath, skipForAvailableLocales = false) { this.name = name; this.locales = locales; this.prePath = prePath; + this.skipForAvailableLocales = skipForAvailableLocales; this.indexed = false;
// The cache object stores information about the resources available
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 20cbc7a61c8c54eab41c7eb6d44f2ba024660cc3 Author: Alex Catarineu acat@torproject.org AuthorDate: Wed May 29 20:04:37 2019 +0200
Bring back old Firefox onboarding
Revert "Bug 1462415 - Delete onboarding system add-on r=Standard8,k88hudson"
This reverts commit f7ffd78b62541d44d0102f8051d2f4080bdbc432.
Revert "Bug 1498378 - Actually remove the old onboarding add-on's prefs r=Gijs"
This reverts commit 057fe36fc6f3e93e265505c7dcc703a0941778e2.
Bug 28822: Convert onboarding to webextension
Partially revert 1564367 (controlCenter in UITour.jsm) --- browser/app/profile/firefox.js | 16 + browser/components/BrowserGlue.jsm | 11 - browser/components/uitour/UITour.jsm | 42 + browser/extensions/moz.build | 2 +- .../extensions/onboarding/OnboardingTelemetry.jsm | 578 +++++++ .../extensions/onboarding/OnboardingTourType.jsm | 40 + browser/extensions/onboarding/README.md | 87 ++ browser/extensions/onboarding/api.js | 238 +++ browser/extensions/onboarding/background.js | 8 + .../extensions/onboarding/content/Onboarding.jsm | 1581 ++++++++++++++++++++ .../onboarding/content/img/figure_addons.svg | 1 + .../onboarding/content/img/figure_customize.svg | 561 +++++++ .../onboarding/content/img/figure_default.svg | 1 + .../onboarding/content/img/figure_library.svg | 689 +++++++++ .../onboarding/content/img/figure_performance.svg | 1 + .../onboarding/content/img/figure_private.svg | 1 + .../onboarding/content/img/figure_screenshots.svg | 191 +++ .../onboarding/content/img/figure_singlesearch.svg | 1 + .../onboarding/content/img/figure_sync.svg | 1 + .../onboarding/content/img/icons_addons.svg | 1 + .../onboarding/content/img/icons_customize.svg | 1 + .../onboarding/content/img/icons_default.svg | 1 + .../onboarding/content/img/icons_library.svg | 1 + .../onboarding/content/img/icons_performance.svg | 1 + .../onboarding/content/img/icons_private.svg | 1 + .../onboarding/content/img/icons_screenshots.svg | 1 + .../onboarding/content/img/icons_singlesearch.svg | 1 + .../onboarding/content/img/icons_sync.svg | 1 + .../onboarding/content/img/icons_tour-complete.svg | 17 + .../onboarding/content/img/watermark.svg | 1 + .../onboarding/content/onboarding-tour-agent.js | 94 ++ .../extensions/onboarding/content/onboarding.css | 589 ++++++++ .../extensions/onboarding/content/onboarding.js | 37 + browser/extensions/onboarding/data_events.md | 154 ++ browser/extensions/onboarding/jar.mn | 14 + .../onboarding/locales/en-US/onboarding.properties | 126 ++ .../{moz.build => onboarding/locales/jar.mn} | 12 +- .../extensions/{ => onboarding/locales}/moz.build | 7 +- browser/extensions/onboarding/manifest.json | 26 + browser/extensions/onboarding/moz.build | 26 + browser/extensions/onboarding/schema.json | 1 + .../onboarding/test/browser/.eslintrc.js | 7 + .../extensions/onboarding/test/browser/browser.ini | 18 + .../browser/browser_onboarding_accessibility.js | 89 ++ .../test/browser/browser_onboarding_keyboard.js | 137 ++ .../browser/browser_onboarding_notification.js | 62 + .../browser/browser_onboarding_notification_2.js | 80 + .../browser/browser_onboarding_notification_3.js | 82 + .../browser/browser_onboarding_notification_4.js | 84 ++ .../browser/browser_onboarding_notification_5.js | 25 + ...arding_notification_click_auto_complete_tour.js | 33 + .../browser_onboarding_select_default_tour.js | 80 + .../test/browser/browser_onboarding_skip_tour.js | 47 + .../test/browser/browser_onboarding_tours.js | 115 ++ .../test/browser/browser_onboarding_tourset.js | 82 + .../test/browser/browser_onboarding_uitour.js | 167 +++ browser/extensions/onboarding/test/browser/head.js | 288 ++++ .../extensions/onboarding/test/unit/.eslintrc.js | 7 + browser/extensions/onboarding/test/unit/head.js | 54 + .../test/unit/test-onboarding-tour-type.js | 89 ++ .../extensions/onboarding/test/unit/xpcshell.ini | 5 + browser/installer/package-manifest.in | 1 + browser/locales/Makefile.in | 2 + browser/locales/filter.py | 1 + browser/locales/l10n.ini | 1 + browser/locales/l10n.toml | 4 + extensions/permissions/PermissionManager.cpp | 6 +- tools/lint/codespell.yml | 1 + 68 files changed, 6703 insertions(+), 27 deletions(-)
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 8ace92e9bf07f..3f90fcad36029 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -2040,6 +2040,22 @@ pref("browser.sessionstore.restore_tabs_lazily", true);
pref("browser.suppress_first_window_animation", true);
+// Preferences for Photon onboarding system extension +pref("browser.onboarding.enabled", true); +// Mark this as an upgraded profile so we don't offer the initial new user onboarding tour. +pref("browser.onboarding.tourset-version", 2); +pref("browser.onboarding.state", "default"); +// On the Activity-Stream page, the snippet's position overlaps with our notification. +// So use `browser.onboarding.notification.finished` to let the AS page know +// if our notification is finished and safe to show their snippet. +pref("browser.onboarding.notification.finished", false); +pref("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000); // 5 mins +pref("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000); // 5 days +pref("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000); // 14 days +pref("browser.onboarding.notification.max-prompt-count-per-tour", 8); +pref("browser.onboarding.newtour", "performance,private,screenshots,addons,customize,default"); +pref("browser.onboarding.updatetour", "performance,library,screenshots,singlesearch,customize,sync"); + // Preference that allows individual users to disable Screenshots. pref("extensions.screenshots.disabled", false);
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index a765b6f9ee2d6..2170fe472a952 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -3359,17 +3359,6 @@ BrowserGlue.prototype = { } }
- if (currentUIVersion < 76) { - // Clear old onboarding prefs from profile (bug 1462415) - let onboardingPrefs = Services.prefs.getBranch("browser.onboarding."); - if (onboardingPrefs) { - let onboardingPrefsArray = onboardingPrefs.getChildList(""); - for (let item of onboardingPrefsArray) { - Services.prefs.clearUserPref("browser.onboarding." + item); - } - } - } - if (currentUIVersion < 77) { // Remove currentset from all the toolbars let toolbars = [ diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index 807de6e22beaf..29e8944e0e99b 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -845,6 +845,14 @@ var UITour = { ["ViewShowing", this.onAppMenuSubviewShowing], ], }, + { + name: "controlCenter", + node: aWindow.gIdentityHandler._identityPopup, + events: [ + ["popuphidden", this.onPanelHidden], + ["popuphiding", this.onControlCenterHiding], + ], + }, ]; for (let panel of panels) { // Ensure the menu panel is hidden and clean up panel listeners after calling hideMenu. @@ -1439,6 +1447,31 @@ var UITour = { } else if (aMenuName == "bookmarks") { let menuBtn = aWindow.document.getElementById("bookmarks-menu-button"); openMenuButton(menuBtn); + } else if (aMenuName == "controlCenter") { + let popup = aWindow.gIdentityHandler._identityPopup; + + // Add the listener even if the panel is already open since it will still + // only get registered once even if it was UITour that opened it. + popup.addEventListener("popuphiding", this.onControlCenterHiding); + popup.addEventListener("popuphidden", this.onPanelHidden); + + popup.setAttribute("noautohide", "true"); + this.clearAvailableTargetsCache(); + + if (popup.state == "open") { + if (aOpenCallback) { + aOpenCallback(); + } + return; + } + + this.recreatePopup(popup); + + // Open the control center + if (aOpenCallback) { + popup.addEventListener("popupshown", aOpenCallback, { once: true }); + } + aWindow.document.getElementById("identity-box").click(); } else if (aMenuName == "pocket") { let button = aWindow.document.getElementById("save-to-pocket-button"); if (!button) { @@ -1485,6 +1518,9 @@ var UITour = { } else if (aMenuName == "bookmarks") { let menuBtn = aWindow.document.getElementById("bookmarks-menu-button"); closeMenuButton(menuBtn); + } else if (aMenuName == "controlCenter") { + let panel = aWindow.gIdentityHandler._identityPopup; + panel.hidePopup(); } else if (aMenuName == "urlbar") { aWindow.gURLBar.view.close(); } @@ -1563,6 +1599,12 @@ var UITour = { UITour._hideAnnotationsForPanel(aEvent, false, UITour.targetIsInAppMenu); },
+ onControlCenterHiding(aEvent) { + UITour._hideAnnotationsForPanel(aEvent, true, aTarget => { + return aTarget.targetName.startsWith("controlCenter-"); + }); + }, + onPanelHidden(aEvent) { aEvent.target.removeAttribute("noautohide"); UITour.recreatePopup(aEvent.target); diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build index ab735cf2688f4..339702b90a8a9 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DIRS += [] +DIRS += ["onboarding"]
if CONFIG["NIGHTLY_BUILD"]: DIRS += [ diff --git a/browser/extensions/onboarding/OnboardingTelemetry.jsm b/browser/extensions/onboarding/OnboardingTelemetry.jsm new file mode 100644 index 0000000000000..494c8d246d87e --- /dev/null +++ b/browser/extensions/onboarding/OnboardingTelemetry.jsm @@ -0,0 +1,578 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["OnboardingTelemetry"]; + +ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetters(this, { + PingCentre: "resource:///modules/PingCentre.jsm", +}); +XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", + "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"); + +// Validate the content has non-empty string +function hasString(str) { + return typeof str == "string" && str.length > 0; +} + +// Validate the content is an empty string +function isEmptyString(str) { + return typeof str == "string" && str === ""; +} + +// Validate the content is an interger +function isInteger(i) { + return Number.isInteger(i); +} + +// Validate the content is a positive interger +function isPositiveInteger(i) { + return Number.isInteger(i) && i > 0; +} + +// Validate the number is -1 +function isMinusOne(num) { + return num === -1; +} + +// Validate the category value is within the list +function isValidCategory(category) { + return ["logo-interactions", "onboarding-interactions", + "overlay-interactions", "notification-interactions"] + .includes(category); +} + +// Validate the page value is within the list +function isValidPage(page) { + return ["about:newtab", "about:home", "about:welcome"].includes(page); +} + +// Validate the tour_type value is within the list +function isValidTourType(type) { + return ["new", "update"].includes(type); +} + +// Validate the bubble state value is within the list +function isValidBubbleState(str) { + return ["bubble", "dot", "hide"].includes(str); +} + +// Validate the logo state value is within the list +function isValidLogoState(str) { + return ["logo", "watermark"].includes(str); +} + +// Validate the notification state value is within the list +function isValidNotificationState(str) { + return ["show", "hide", "finished"].includes(str); +} + +// Validate the column must be defined per ping +function definePerPing(column) { + return function() { + throw new Error(`Must define the '${column}' validator per ping because it is not the same for all pings`); + }; +} + +// Basic validators for session pings +// client_id, locale are added by PingCentre, IP is added by server +// so no need check these column here +const BASIC_SESSION_SCHEMA = { + addon_version: hasString, + category: isValidCategory, + page: isValidPage, + parent_session_id: hasString, + root_session_id: hasString, + session_begin: isInteger, + session_end: isInteger, + session_id: hasString, + tour_type: isValidTourType, + type: hasString, +}; + +// Basic validators for event pings +// client_id, locale are added by PingCentre, IP is added by server +// so no need check these column here +const BASIC_EVENT_SCHEMA = { + addon_version: hasString, + bubble_state: definePerPing("bubble_state"), + category: isValidCategory, + current_tour_id: definePerPing("current_tour_id"), + logo_state: definePerPing("logo_state"), + notification_impression: definePerPing("notification_impression"), + notification_state: definePerPing("notification_state"), + page: isValidPage, + parent_session_id: hasString, + root_session_id: hasString, + target_tour_id: definePerPing("target_tour_id"), + timestamp: isInteger, + tour_type: isValidTourType, + type: hasString, + width: isPositiveInteger, +}; + +/** + * We send 2 kinds (firefox-onboarding-event2, firefox-onboarding-session2) of pings to ping centre + * server (they call it `topic`). The `internal` state in `topic` field means this event is used internaly to + * track states and will not send out any message. + * + * To save server space and make query easier, we track session begin and end but only send pings + * when session end. Therefore the server will get single "onboarding/overlay/notification-session" + * event which includes both `session_begin` and `session_end` timestamp. + * + * We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because + * of analytics engineer's request. + */ +const EVENT_WHITELIST = { + // track when a notification appears. + "notification-appear": { + topic: "firefox-onboarding-event2", + category: "notification-interactions", + parent: "notification-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isValidBubbleState, + current_tour_id: hasString, + logo_state: isValidLogoState, + notification_impression: isPositiveInteger, + notification_state: isValidNotificationState, + target_tour_id: isEmptyString, + }), + }, + // track when a user clicks close notification button + "notification-close-button-click": { + topic: "firefox-onboarding-event2", + category: "notification-interactions", + parent: "notification-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isValidBubbleState, + current_tour_id: hasString, + logo_state: isValidLogoState, + notification_impression: isPositiveInteger, + notification_state: isValidNotificationState, + target_tour_id: hasString, + }), + }, + // track when a user clicks notification's Call-To-Action button + "notification-cta-click": { + topic: "firefox-onboarding-event2", + category: "notification-interactions", + parent: "notification-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isValidBubbleState, + current_tour_id: hasString, + logo_state: isValidLogoState, + notification_impression: isPositiveInteger, + notification_state: isValidNotificationState, + target_tour_id: hasString, + }), + }, + // track the start and end time of the notification session + "notification-session": { + topic: "firefox-onboarding-session2", + category: "notification-interactions", + parent: "onboarding-session", + validators: BASIC_SESSION_SCHEMA, + }, + // track the start of a notification + "notification-session-begin": {topic: "internal"}, + // track the end of a notification + "notification-session-end": {topic: "internal"}, + // track when a user clicks the Firefox logo + "onboarding-logo-click": { + topic: "firefox-onboarding-event2", + category: "logo-interactions", + parent: "onboarding-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isValidBubbleState, + current_tour_id: isEmptyString, + logo_state: isValidLogoState, + notification_impression: isMinusOne, + notification_state: isValidNotificationState, + target_tour_id: isEmptyString, + }), + }, + // track when the onboarding is not visisble due to small screen in the 1st load + "onboarding-noshow-smallscreen": { + topic: "firefox-onboarding-event2", + category: "onboarding-interactions", + parent: "onboarding-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: isEmptyString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: isEmptyString, + }), + }, + // init onboarding session with session_key, page url, and tour_type + "onboarding-register-session": {topic: "internal"}, + // track the start and end time of the onboarding session + "onboarding-session": { + topic: "firefox-onboarding-session2", + category: "onboarding-interactions", + parent: "onboarding-session", + validators: BASIC_SESSION_SCHEMA, + }, + // track onboarding start time (when user loads about:home or about:newtab) + "onboarding-session-begin": {topic: "internal"}, + // track onboarding end time (when user unloads about:home or about:newtab) + "onboarding-session-end": {topic: "internal"}, + // track when a user clicks the close overlay button + "overlay-close-button-click": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: hasString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: hasString, + }), + }, + // track when a user clicks outside the overlay area to end the tour + "overlay-close-outside-click": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: hasString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: hasString, + }), + }, + // track when a user clicks overlay's Call-To-Action button + "overlay-cta-click": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: hasString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: hasString, + }), + }, + // track when a tour is shown in the overlay + "overlay-current-tour": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: hasString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: isEmptyString, + }), + }, + // track when an overlay is opened and disappeared because the window is resized too small + "overlay-disapear-resize": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: isEmptyString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: isEmptyString, + }), + }, + // track when a user clicks a navigation button in the overlay + "overlay-nav-click": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: hasString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: hasString, + }), + }, + // track the start and end time of the overlay session + "overlay-session": { + topic: "firefox-onboarding-session2", + category: "overlay-interactions", + parent: "onboarding-session", + validators: BASIC_SESSION_SCHEMA, + }, + // track the start of an overlay session + "overlay-session-begin": {topic: "internal"}, + // track the end of an overlay session + "overlay-session-end": {topic: "internal"}, + // track when a user clicks 'Skip Tour' button in the overlay + "overlay-skip-tour": { + topic: "firefox-onboarding-event2", + category: "overlay-interactions", + parent: "overlay-session", + validators: Object.assign({}, BASIC_EVENT_SCHEMA, { + bubble_state: isEmptyString, + current_tour_id: hasString, + logo_state: isEmptyString, + notification_impression: isMinusOne, + notification_state: isEmptyString, + target_tour_id: isEmptyString, + }), + }, +}; + +const ONBOARDING_ID = "onboarding"; + +let OnboardingTelemetry = { + sessionProbe: null, + eventProbe: null, + state: { + sessions: {}, + }, + + init(startupData) { + this.sessionProbe = new PingCentre({topic: "firefox-onboarding-session2"}); + this.eventProbe = new PingCentre({topic: "firefox-onboarding-event2"}); + this.state.addon_version = startupData.version; + }, + + // register per tab session data + registerNewOnboardingSession(data) { + let { page, session_key, tour_type } = data; + if (this.state.sessions[session_key]) { + return; + } + // session_key and page url are must have + if (!session_key || !page || !tour_type) { + throw new Error("session_key, page url, and tour_type are required for onboarding-register-session"); + } + let onboarding_session_id = gUUIDGenerator.generateUUID().toString(); + this.state.sessions[session_key] = { + onboarding_session_id, + overlay_session_id: "", + notification_session_id: "", + page, + tour_type, + }; + }, + + process(data) { + let { type, session_key } = data; + if (type === "onboarding-register-session") { + this.registerNewOnboardingSession(data); + return; + } + + if (!this.state.sessions[session_key]) { + throw new Error(`${type} should pass valid session_key`); + } + + switch (type) { + case "onboarding-session-begin": + if (!this.state.sessions[session_key].onboarding_session_id) { + throw new Error(`should fire onboarding-register-session event before ${type}`); + } + this.state.sessions[session_key].onboarding_session_begin = Date.now(); + return; + case "onboarding-session-end": + data = Object.assign({}, data, { + type: "onboarding-session", + }); + this.state.sessions[session_key].onboarding_session_end = Date.now(); + break; + case "overlay-session-begin": + this.state.sessions[session_key].overlay_session_id = gUUIDGenerator.generateUUID().toString(); + this.state.sessions[session_key].overlay_session_begin = Date.now(); + return; + case "overlay-session-end": + data = Object.assign({}, data, { + type: "overlay-session", + }); + this.state.sessions[session_key].overlay_session_end = Date.now(); + break; + case "notification-session-begin": + this.state.sessions[session_key].notification_session_id = gUUIDGenerator.generateUUID().toString(); + this.state.sessions[session_key].notification_session_begin = Date.now(); + return; + case "notification-session-end": + data = Object.assign({}, data, { + type: "notification-session", + }); + this.state.sessions[session_key].notification_session_end = Date.now(); + break; + } + let topic = EVENT_WHITELIST[data.type] && EVENT_WHITELIST[data.type].topic; + if (!topic) { + throw new Error(`ping-centre doesn't know ${type} after processPings, only knows ${Object.keys(EVENT_WHITELIST)}`); + } + this._sendPing(topic, data); + }, + + // send out pings by topic + _sendPing(topic, data) { + if (topic === "internal") { + throw new Error(`internal ping ${data.type} should be processed within processPings`); + } + + let { + addon_version, + } = this.state; + let { + bubble_state = "", + current_tour_id = "", + logo_state = "", + notification_impression = -1, + notification_state = "", + session_key, + target_tour_id = "", + type, + width, + } = data; + let { + notification_session_begin, + notification_session_end, + notification_session_id, + onboarding_session_begin, + onboarding_session_end, + onboarding_session_id, + overlay_session_begin, + overlay_session_end, + overlay_session_id, + page, + tour_type, + } = this.state.sessions[session_key]; + let { + category, + parent, + } = EVENT_WHITELIST[type]; + let parent_session_id; + let payload; + let session_begin; + let session_end; + let session_id; + let root_session_id = onboarding_session_id; + + // assign parent_session_id + switch (parent) { + case "onboarding-session": + parent_session_id = onboarding_session_id; + break; + case "overlay-session": + parent_session_id = overlay_session_id; + break; + case "notification-session": + parent_session_id = notification_session_id; + break; + } + if (!parent_session_id) { + throw new Error(`Unable to find the ${parent} parent session for the event ${type}`); + } + + switch (topic) { + case "firefox-onboarding-session2": + switch (type) { + case "onboarding-session": + session_id = onboarding_session_id; + session_begin = onboarding_session_begin; + session_end = onboarding_session_end; + delete this.state.sessions[session_key]; + break; + case "overlay-session": + session_id = overlay_session_id; + session_begin = overlay_session_begin; + session_end = overlay_session_end; + break; + case "notification-session": + session_id = notification_session_id; + session_begin = notification_session_begin; + session_end = notification_session_end; + break; + } + if (!session_id || !session_begin || !session_end) { + throw new Error(`should fire ${type}-begin and ${type}-end event before ${type}`); + } + + payload = { + addon_version, + category, + page, + parent_session_id, + root_session_id, + session_begin, + session_end, + session_id, + tour_type, + type, + }; + this._validatePayload(payload); + this.sessionProbe && this.sessionProbe.sendPing(payload, + {filter: ONBOARDING_ID}); + break; + case "firefox-onboarding-event2": + let timestamp = Date.now(); + payload = { + addon_version, + bubble_state, + category, + current_tour_id, + logo_state, + notification_impression, + notification_state, + page, + parent_session_id, + root_session_id, + target_tour_id, + timestamp, + tour_type, + type, + width, + }; + this._validatePayload(payload); + this.eventProbe && this.eventProbe.sendPing(payload, + {filter: ONBOARDING_ID}); + break; + } + }, + + // validate data sanitation and make sure correct ping params are sent + _validatePayload(payload) { + let type = payload.type; + let { validators } = EVENT_WHITELIST[type]; + if (!validators) { + throw new Error(`Event ${type} without validators should not be sent.`); + } + let validatorKeys = Object.keys(validators); + // Not send with undefined column + if (Object.keys(payload).length > validatorKeys.length) { + throw new Error(`Event ${type} want to send more columns than expect, should not be sent.`); + } + let results = {}; + let failed = false; + // Per column validation + for (let key of validatorKeys) { + if (payload[key] !== undefined) { + results[key] = validators[key](payload[key]); + if (!results[key]) { + failed = true; + } + } else { + results[key] = false; + failed = true; + } + } + if (failed) { + throw new Error(`Event ${type} contains incorrect data: ${JSON.stringify(results)}, should not be sent.`); + } + }, +}; diff --git a/browser/extensions/onboarding/OnboardingTourType.jsm b/browser/extensions/onboarding/OnboardingTourType.jsm new file mode 100644 index 0000000000000..d984fc42f390f --- /dev/null +++ b/browser/extensions/onboarding/OnboardingTourType.jsm @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["OnboardingTourType"]; + +ChromeUtils.defineModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +var OnboardingTourType = { + /** + * Determine the current tour type (new user tour or update user tour). + * The function checks 2 criterias + * - TOURSET_VERSION: current onboarding tourset version + * - PREF_SEEN_TOURSET_VERSION: the user seen tourset version + * As the result the function will set the right current tour type in the tour type pref (PREF_TOUR_TYPE) for later use. + */ + check() { + const PREF_TOUR_TYPE = "browser.onboarding.tour-type"; + const PREF_SEEN_TOURSET_VERSION = "browser.onboarding.seen-tourset-version"; + const TOURSET_VERSION = Services.prefs.getIntPref("browser.onboarding.tourset-version"); + + if (!Services.prefs.prefHasUserValue(PREF_SEEN_TOURSET_VERSION)) { + // User has never seen an onboarding tour, present the user with the new user tour. + Services.prefs.setStringPref(PREF_TOUR_TYPE, "new"); + } else if (Services.prefs.getIntPref(PREF_SEEN_TOURSET_VERSION) < TOURSET_VERSION) { + // show the update user tour when tour set version is larger than the seen tourset version + Services.prefs.setStringPref(PREF_TOUR_TYPE, "update"); + // Reset all the notification-related prefs because tours update. + Services.prefs.setBoolPref("browser.onboarding.notification.finished", false); + Services.prefs.clearUserPref("browser.onboarding.notification.prompt-count"); + Services.prefs.clearUserPref("browser.onboarding.notification.last-time-of-changing-tour-sec"); + Services.prefs.clearUserPref("browser.onboarding.notification.tour-ids-queue"); + Services.prefs.clearUserPref("browser.onboarding.state"); + } + Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION); + }, +}; diff --git a/browser/extensions/onboarding/README.md b/browser/extensions/onboarding/README.md new file mode 100644 index 0000000000000..c63be42b71818 --- /dev/null +++ b/browser/extensions/onboarding/README.md @@ -0,0 +1,87 @@ +# Onboarding + +System addon to provide the onboarding overlay for user-friendly tours. + +## How to show the onboarding tour + +Open `about:config` page and filter with `onboarding` keyword. Then set following preferences: + +``` +browser.onboarding.disabled = false +browser.onboarding.tour-set = "new" // for new user tour, or "update" for update user tour +``` +And make sure the value of `browser.onboarding.tourset-verion` and `browser.onboarding.seen-tourset-verion` are the same. + +## How to show the onboarding notification + +Besides above settings, notification will wait 5 minutes before showing the first notification on a new profile or the updated user profile (to not put too much information to the user at once). + +To manually remove the mute duration, set pref `browser.onboarding.notification.mute-duration-on-first-session-ms` to `0` and notification will be shown at the next time you open `about:home`, `about:newtab`, or `about:welcome`. + +## How to show the snippets + +Snippets (the remote notification that handled by activity stream) will only be shown after onboarding notifications are all done. You can set preference `browser.onboarding.notification.finished` to `true` to disable onboarding notification and accept snippets right away. + +## Architecture + +![](https://i.imgur.com/7RK89Zw.png) + +During booting from `bootstrap.js`, `OnboardingTourType.jsm` will check the onboarding tour type (`new` and `update` are supported types) and set required initial states into preferences. + +Everytime `about:home`, `about:newtab`, or `about:welcome` page is opened, `onboarding.js` is injected into that page via [frame scripts](https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Message_Man...). + +Then in `onboarding.js`, all tours are defined inside of `onboardingTourset` dictionary. `getTourIDList` function will load tours from proper preferences. (Check `How to change the order of tours` section for more detail). + +When user clicks the action button in each tour, We use [UITour](http://bedrock.readthedocs.io/en/latest/uitour.html) to highlight the correspondent browser UI element. The UITour client is bundled in onboarding addon via `jar.mn`. + +## Landing rules + +We would apply some rules: + +* To avoid conflict with the origin page, all styles and ids should be formatted as `onboarding-*`. +* For consistency and easier filtering, all strings in `locales` should be formatted as `onboarding.*`. +* For consistency, all related preferences should be formatted as `browser.onboarding.*`. +* For accessibility, images that are for presentation only should have `role="presentation"` attribute. + +## How to change the order of tours + +Edit `browser/app/profile/firefox.js` and modify `browser.onboarding.newtour` for the new user tour or `browser.onboarding.updatetour` for the update user tour. You can change the tour list and the order by concate `tourIds` with `,` sign. You can find available `tourId` from `onboardingTourset` in `onboarding.js`. + +## How to pump tour set version after update tours + +We only update the tourset version when we have different **update** tourset. Update the new tourset **does not** require update the tourset version. + +The tourset version is used to track the last major tourset change version. The `tourset-version` pref store the major tourset version (ex: `1`) but not the current browser version. When browser update to the next version (ex: 58, 59) the tourset pref is still `1` if we didn't do any major tourset update. + +Once the tour set version is updated (ex: `2`), onboarding overlay should show the update tour to the updated user (ex: update from v56 -> v57), even when user has watched the previous tours or preferred to hide the previous tours. + +Edit `browser/app/profile/firefox.js` and set `browser.onboarding.tourset-version` as `[tourset version]` (in integer format). + +For example, if we update the tourset in v60 and decide to show all update users the tour, we set `browser.onboarding.tourset-version` as `3`. + +## Icon states + +Onboarding module has two states for its overlay icon: `default` and `watermark`. +By default, it shows `default` state. +When either tours or notifications are all completed, the icon changes to the `watermark` state. +The icon state is stored in `browser.onboarding.state`. +When `tourset-version` is updated, or when we detect the `tour-type` is changed to `update`, icon state will be changed back to the `default` state. + +## Customizable preferences + +Here are current support preferences that allow to customize the Onboarding's behavior. + +| PREF | DESCRIPTION | DEFAULT | +|-----|-------------|:-----:| +| `browser.onboarding.enabled` | disable onboarding experience entirely | true +| `browser.onboarding.notification.finished` | Decide if we want to hide the notification permanently. | false +| `browser.onboarding.notification.mute-duration-on-first-session-ms` |Notification mute duration. It also effect when the speech bubble is hidden and turned into the blue dot | 300000 (5 Min) +| `browser.onboarding.notification.max-life-time-all-tours-ms` | Notification tours will all hide after this period | 1209600000 (10 Days) +| `browser.onboarding.notification.max-life-time-per-tours-ms` | Per Notification tours will hide and show the next tour after this period | 432000000 (5 Days) +| `browser.onboarding.notification.max-prompt-count-per-tour` | Each tour can only show the specific times in notification bar if user didn't interact with the tour notification. | 8 +| `browser.onboarding.newtour` | The tourset of new user tour. | performance,private,screenshots,addons,customize,default +| `browser.onboarding.newtour.tooltip` | The string id which is shown in the new user tour's speech bubble. The preffered length is 2 lines. Should use `%S` to denote Firefox (brand short name) in string, or use `%1$S` if the name shows more than 1 time. | `onboarding.overlay-icon-tooltip2` +| `browser.onboarding.updatetour` | The tourset of new user tour. | performance,library,screenshots,singlesearch,customize,sync +| `browser.onboarding.updatetour.tooltip` | The string id which is shown in the update user tour's speech bubble. The preffered length is 2 lines. Should use `%S` to denote Firefox (brand short name) in string, or use `%1$S` if the name shows shows more than 1 time. | `onboarding.overlay-icon-tooltip-updated2` +| `browser.onboarding.default-icon-src` | The default icon url. Should be svg or at least 64x64 | `chrome://branding/content/icon64.png` +| `browser.onboarding.watermark-icon-src` | The watermark icon url. Should be svg or at least 64x64 | `resource://onboarding/img/watermark.svg` diff --git a/browser/extensions/onboarding/api.js b/browser/extensions/onboarding/api.js new file mode 100644 index 0000000000000..dcdcb988451a2 --- /dev/null +++ b/browser/extensions/onboarding/api.js @@ -0,0 +1,238 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* globals APP_STARTUP, ADDON_INSTALL */ +"use strict"; + +ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetters(this, { + OnboardingTourType: "resource://onboarding/modules/OnboardingTourType.jsm", + OnboardingTelemetry: "resource://onboarding/modules/OnboardingTelemetry.jsm", + Services: "resource://gre/modules/Services.jsm", + UIState: "resource://services-sync/UIState.jsm", +}); + +XPCOMUtils.defineLazyServiceGetter(this, "resProto", + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler"); + +const RESOURCE_HOST = "onboarding"; + +const {PREF_STRING, PREF_BOOL, PREF_INT} = Ci.nsIPrefBranch; + +const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished"; +const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored"; +const PREF_WHITELIST = [ + ["browser.onboarding.enabled", PREF_BOOL], + ["browser.onboarding.state", PREF_STRING], + ["browser.onboarding.notification.finished", PREF_BOOL], + ["browser.onboarding.notification.prompt-count", PREF_INT], + ["browser.onboarding.notification.last-time-of-changing-tour-sec", PREF_INT], + ["browser.onboarding.notification.tour-ids-queue", PREF_STRING], +]; + +[ + "onboarding-tour-addons", + "onboarding-tour-customize", + "onboarding-tour-default-browser", + "onboarding-tour-library", + "onboarding-tour-performance", + "onboarding-tour-private-browsing", + "onboarding-tour-screenshots", + "onboarding-tour-singlesearch", + "onboarding-tour-sync", +].forEach(tourId => PREF_WHITELIST.push([`browser.onboarding.tour.${tourId}.completed`, PREF_BOOL])); + +let waitingForBrowserReady = true; +let startupData; + +/** + * Set pref. Why no `getPrefs` function is due to the privilege level. + * We cannot set prefs inside a framescript but can read. + * For simplicity and efficiency, we still read prefs inside the framescript. + * + * @param {Array} prefs the array of prefs to set. + * The array element carries info to set pref, should contain + * - {String} name the pref name, such as `browser.onboarding.state` + * - {*} value the value to set + **/ +function setPrefs(prefs) { + prefs.forEach(pref => { + let prefObj = PREF_WHITELIST.find(([name ]) => name == pref.name); + if (!prefObj) { + return; + } + + let [name, type] = prefObj; + + switch (type) { + case PREF_BOOL: + Services.prefs.setBoolPref(name, pref.value); + break; + case PREF_INT: + Services.prefs.setIntPref(name, pref.value); + break; + case PREF_STRING: + Services.prefs.setStringPref(name, pref.value); + break; + default: + throw new TypeError(`Unexpected type (${type}) for preference ${name}.`); + } + }); +} + +/** + * syncTourChecker listens to and maintains the login status inside, and can be + * queried at any time once initialized. + */ +let syncTourChecker = { + _registered: false, + _loggedIn: false, + + isLoggedIn() { + return this._loggedIn; + }, + + observe(subject, topic) { + const state = UIState.get(); + if (state.status == UIState.STATUS_NOT_CONFIGURED) { + this._loggedIn = false; + } else { + this.setComplete(); + } + }, + + init() { + if (!Services.prefs.getBoolPref("identity.fxaccounts.enabled")) { + return; + } + // Check if we've already logged in at startup. + const state = UIState.get(); + if (state.status != UIState.STATUS_NOT_CONFIGURED) { + this.setComplete(); + } + this.register(); + }, + + register() { + if (this._registered) { + return; + } + Services.obs.addObserver(this, "sync-ui-state:update"); + this._registered = true; + }, + + setComplete() { + this._loggedIn = true; + Services.prefs.setBoolPref("browser.onboarding.tour.onboarding-tour-sync.completed", true); + }, + + unregister() { + if (!this._registered) { + return; + } + Services.obs.removeObserver(this, "sync-ui-state:update"); + this._registered = false; + }, + + uninit() { + this.unregister(); + }, +}; + +/** + * Listen and process events from content. + */ +function initContentMessageListener() { + Services.mm.addMessageListener("Onboarding:OnContentMessage", msg => { + switch (msg.data.action) { + case "set-prefs": + setPrefs(msg.data.params); + break; + case "get-login-status": + msg.target.messageManager.sendAsyncMessage("Onboarding:ResponseLoginStatus", { + isLoggedIn: syncTourChecker.isLoggedIn(), + }); + break; + case "ping-centre": + try { + OnboardingTelemetry.process(msg.data.params.data); + } catch (e) { + Cu.reportError(e); + } + break; + } + }); +} + +/** + * onBrowserReady - Continues startup of the add-on after browser is ready. + */ +function onBrowserReady() { + waitingForBrowserReady = false; + + OnboardingTourType.check(); + OnboardingTelemetry.init(startupData); + Services.mm.loadFrameScript("resource://onboarding/onboarding.js", true); + initContentMessageListener(); +} + +/** + * observe - nsIObserver callback to handle various browser notifications. + */ +function observe(subject, topic, data) { + switch (topic) { + case BROWSER_READY_NOTIFICATION: + Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION); + onBrowserReady(); + break; + case BROWSER_SESSION_STORE_NOTIFICATION: + Services.obs.removeObserver(observe, BROWSER_SESSION_STORE_NOTIFICATION); + // Postpone Firefox account checking until "before handling user events" + // phase to meet performance criteria. The reason we don't postpone the + // whole onBrowserReady here is because in that way we will miss onload + // events for onboarding.js. + Services.tm.idleDispatchToMainThread(() => syncTourChecker.init()); + break; + } +} + +this.onboarding = class extends ExtensionAPI { + onStartup() { + resProto.setSubstitutionWithFlags(RESOURCE_HOST, + Services.io.newURI("chrome/content/", null, this.extension.rootURI), + resProto.ALLOW_CONTENT_ACCESS); + + if (this.extension.rootURI instanceof Ci.nsIJARURI) { + this.manifest = this.extension.rootURI.JARFile.QueryInterface(Ci.nsIFileURL).file; + } else if (this.extension.rootURI instanceof Ci.nsIFileURL) { + this.manifest = this.extension.rootURI.file; + } + + if (this.manifest) { + Components.manager.addBootstrappedManifestLocation(this.manifest); + } else { + Cu.reportError("Cannot find onboarding chrome.manifest for registring translated strings"); + } + + // Only start Onboarding when the browser UI is ready + if (Services.startup.startingUp) { + Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION); + Services.obs.addObserver(observe, BROWSER_SESSION_STORE_NOTIFICATION); + } else { + onBrowserReady(); + syncTourChecker.init(); + } + } + + onShutdown() { + resProto.setSubstitution(RESOURCE_HOST, null); + + // Stop waiting for browser to be ready + if (waitingForBrowserReady) { + Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION); + } + syncTourChecker.uninit(); + } +}; diff --git a/browser/extensions/onboarding/background.js b/browser/extensions/onboarding/background.js new file mode 100644 index 0000000000000..efe296ff22781 --- /dev/null +++ b/browser/extensions/onboarding/background.js @@ -0,0 +1,8 @@ +/* eslint-env webextensions */ + +"use strict"; + +browser.runtime.onUpdateAvailable.addListener(details => { + // By listening to but ignoring this event, any updates will + // be delayed until the next browser restart. +}); diff --git a/browser/extensions/onboarding/content/Onboarding.jsm b/browser/extensions/onboarding/content/Onboarding.jsm new file mode 100644 index 0000000000000..de95a66632ab3 --- /dev/null +++ b/browser/extensions/onboarding/content/Onboarding.jsm @@ -0,0 +1,1581 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/frame-script */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["Onboarding"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css"; +const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties"; +const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js"; +const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js"; +const BRAND_SHORT_NAME = Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandShortName"); +const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count"; +const NOTIFICATION_FINISHED_PREF = "browser.onboarding.notification.finished"; +const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog"; +const ONBOARDING_MIN_WIDTH_PX = 960; +const SPEECH_BUBBLE_MIN_WIDTH_PX = 1365; +const SPEECH_BUBBLE_NEWTOUR_STRING_ID = "onboarding.overlay-icon-tooltip2"; +const SPEECH_BUBBLE_UPDATETOUR_STRING_ID = "onboarding.overlay-icon-tooltip-updated2"; +const ICON_STATE_WATERMARK = "watermark"; +const ICON_STATE_DEFAULT = "default"; + +/** + * Helper function to create the tour description UI element. + */ +function createOnboardingTourDescription(div, title, description) { + let doc = div.ownerDocument; + let section = doc.createElement("section"); + section.className = "onboarding-tour-description"; + + let h1 = doc.createElement("h1"); + h1.setAttribute("data-l10n-id", title); + section.appendChild(h1); + + let p = doc.createElement("p"); + p.setAttribute("data-l10n-id", description); + section.appendChild(p); + + div.appendChild(section); + return section; +} + +/** + * Helper function to create the tour content UI element. + */ +function createOnboardingTourContent(div, imageSrc) { + let doc = div.ownerDocument; + let section = doc.createElement("section"); + section.className = "onboarding-tour-content"; + + let img = doc.createElement("img"); + img.setAttribute("src", imageSrc); + img.setAttribute("role", "presentation"); + section.appendChild(img); + + div.appendChild(section); + return section; +} + +/** + * Helper function to create the tour button UI element. + */ +function createOnboardingTourButton(div, buttonId, l10nId, buttonElementTagName = "button") { + let doc = div.ownerDocument; + let aside = doc.createElement("aside"); + aside.className = "onboarding-tour-button-container"; + + let button = doc.createElement(buttonElementTagName); + button.id = buttonId; + button.className = "onboarding-tour-action-button"; + button.setAttribute("data-l10n-id", l10nId); + aside.appendChild(button); + + div.appendChild(aside); + return aside; +} + +/** + * Add any number of tours, key is the tourId, value should follow the format below + * "tourId": { // The short tour id which could be saved in pref + * // The unique tour id + * id: "onboarding-tour-addons", + * // (optional) mark tour as complete instantly when the user enters the tour + * instantComplete: false, + * // The string id of tour name which would be displayed on the navigation bar + * tourNameId: "onboarding.tour-addon", + * // The method returing strings used on tour notification + * getNotificationStrings(bundle): + * - title: // The string of tour notification title + * - message: // The string of tour notification message + * - button: // The string of tour notification action button title + * // Return a div appended with elements for this tours. + * // Each tour should contain the following 3 sections in the div: + * // .onboarding-tour-description, .onboarding-tour-content, .onboarding-tour-button-container. + * // If there was a .onboarding-tour-action-button present and was clicked, tour would be marked as completed. + * getPage() {}, + * }, + **/ +var onboardingTourset = { + "private": { + id: "onboarding-tour-private-browsing", + tourNameId: "onboarding.tour-private-browsing", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.title"), + message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message2"), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-private-browsing.title2", "onboarding.tour-private-browsing.description3"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_private.svg"); + createOnboardingTourButton(div, + "onboarding-tour-private-browsing-button", "onboarding.tour-private-browsing.button"); + + return div; + }, + }, + "addons": { + id: "onboarding-tour-addons", + tourNameId: "onboarding.tour-addons", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-addons.title"), + message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-addons.message", [BRAND_SHORT_NAME], 1), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-addons.title2", "onboarding.tour-addons.description2"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_addons.svg"); + createOnboardingTourButton(div, + "onboarding-tour-addons-button", "onboarding.tour-addons.button"); + + return div; + }, + }, + "customize": { + id: "onboarding-tour-customize", + tourNameId: "onboarding.tour-customize", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-customize.title"), + message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-customize.message", [BRAND_SHORT_NAME], 1), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-customize.title2", "onboarding.tour-customize.description2"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_customize.svg"); + createOnboardingTourButton(div, + "onboarding-tour-customize-button", "onboarding.tour-customize.button"); + + return div; + }, + }, + "default": { + id: "onboarding-tour-default-browser", + instantComplete: true, + tourNameId: "onboarding.tour-default-browser", + getNotificationStrings(bundle) { + return { + title: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.title", [BRAND_SHORT_NAME], 1), + message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.message", [BRAND_SHORT_NAME], 1), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win, bundle) { + let div = win.document.createElement("div"); + let setFromBackGround = bundle.formatStringFromName("onboarding.tour-default-browser.win7.button", [BRAND_SHORT_NAME], 1); + let setFromPanel = bundle.GetStringFromName("onboarding.tour-default-browser.button"); + let isDefaultMessage = bundle.GetStringFromName("onboarding.tour-default-browser.is-default.message"); + let isDefault2ndMessage = bundle.formatStringFromName("onboarding.tour-default-browser.is-default.2nd-message", [BRAND_SHORT_NAME], 1); + + createOnboardingTourDescription(div, + "onboarding.tour-default-browser.title2", "onboarding.tour-default-browser.description2"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_default.svg"); + + let aside = win.document.createElement("aside"); + aside.className = "onboarding-tour-button-container"; + div.appendChild(aside); + + let button = win.document.createElement("button"); + button.id = "onboarding-tour-default-browser-button"; + button.className = "onboarding-tour-action-button"; + button.setAttribute("data-bg", setFromBackGround); + button.setAttribute("data-panel", setFromPanel); + aside.appendChild(button); + + let isDefaultBrowserMsg = win.document.createElement("div"); + isDefaultBrowserMsg.id = "onboarding-tour-is-default-browser-msg"; + isDefaultBrowserMsg.className = "onboarding-hidden"; + aside.appendChild(isDefaultBrowserMsg); + isDefaultBrowserMsg.append(isDefaultMessage); + + let br = win.document.createElement("br"); + isDefaultBrowserMsg.appendChild(br); + isDefaultBrowserMsg.append(isDefault2ndMessage); + + div.addEventListener("beforeshow", () => { + win.document.dispatchEvent(new Event("Agent:CanSetDefaultBrowserInBackground")); + }); + return div; + }, + }, + "sync": { + id: "onboarding-tour-sync", + instantComplete: true, + tourNameId: "onboarding.tour-sync2", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.title"), + message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.message"), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win, bundle) { + const STATE_LOGOUT = "logged-out"; + const STATE_LOGIN = "logged-in"; + let div = win.document.createElement("div"); + div.dataset.loginState = STATE_LOGOUT; + // The email validation pattern used in the form comes from IETF rfc5321, + // which is identical to server-side checker of Firefox Account. See + // discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1378770#c6 + // for detail. + let emailRegex = "^[\w.!#$%&’*+\/=?^`{|}~-]{1,64}@[a-z\d](?:[a-z\d-]{0,253}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,253}[a-z\d])?)+$"; + + let description = createOnboardingTourDescription(div, + "onboarding.tour-sync.title2", "onboarding.tour-sync.description2"); + + description.querySelector("h1").className = "show-on-logged-out"; + description.querySelector("p").className = "show-on-logged-out"; + + let h1LoggedIn = win.document.createElement("h1"); + h1LoggedIn.setAttribute("data-l10n-id", "onboarding.tour-sync.logged-in.title"); + h1LoggedIn.className = "show-on-logged-in"; + description.appendChild(h1LoggedIn); + + let pLoggedIn = win.document.createElement("p"); + pLoggedIn.setAttribute("data-l10n-id", "onboarding.tour-sync.logged-in.description"); + pLoggedIn.className = "show-on-logged-in"; + description.appendChild(pLoggedIn); + + let content = win.document.createElement("section"); + content.className = "onboarding-tour-content"; + div.appendChild(content); + + let form = win.document.createElement("form"); + form.className = "show-on-logged-out"; + content.appendChild(form); + + let h3 = win.document.createElement("h3"); + h3.setAttribute("data-l10n-id", "onboarding.tour-sync.form.title"); + form.appendChild(h3); + + let p = win.document.createElement("p"); + p.setAttribute("data-l10n-id", "onboarding.tour-sync.form.description"); + form.appendChild(p); + + let input = win.document.createElement("input"); + input.id = "onboarding-tour-sync-email-input"; + input.setAttribute("required", "true"); + input.setAttribute("type", "email"); + input.placeholder = + bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder"); + input.pattern = emailRegex; + form.appendChild(input); + + let br = win.document.createElement("br"); + form.appendChild(br); + + let button = win.document.createElement("button"); + button.id = "onboarding-tour-sync-button"; + button.className = "onboarding-tour-action-button"; + button.setAttribute("data-l10n-id", "onboarding.tour-sync.button"); + form.appendChild(button); + + let img = win.document.createElement("img"); + img.setAttribute("src", "resource://onboarding/img/figure_sync.svg"); + img.setAttribute("role", "presentation"); + content.appendChild(img); + + let aside = win.document.createElement("aside"); + aside.className = "onboarding-tour-button-container show-on-logged-in"; + div.appendChild(aside); + + let connectDeviceButton = win.document.createElement("button"); + connectDeviceButton.id = "onboarding-tour-sync-connect-device-button"; + connectDeviceButton.className = "onboarding-tour-action-button"; + connectDeviceButton.setAttribute("data-l10n-id", "onboarding.tour-sync.connect-device.button"); + aside.appendChild(connectDeviceButton); + + div.addEventListener("beforeshow", () => { + function loginStatusListener(msg) { + removeMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener); + div.dataset.loginState = msg.data.isLoggedIn ? STATE_LOGIN : STATE_LOGOUT; + } + this.sendMessageToChrome("get-login-status"); + this.mm.addMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener); + }); + + return div; + }, + }, + "library": { + id: "onboarding-tour-library", + tourNameId: "onboarding.tour-library", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-library.title"), + message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-library.message", [BRAND_SHORT_NAME], 1), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-library.title", "onboarding.tour-library.description2"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_library.svg"); + createOnboardingTourButton(div, + "onboarding-tour-library-button", "onboarding.tour-library.button2"); + + return div; + }, + }, + "singlesearch": { + id: "onboarding-tour-singlesearch", + tourNameId: "onboarding.tour-singlesearch", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-singlesearch.title"), + message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-singlesearch.message"), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win, bundle) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-singlesearch.title", "onboarding.tour-singlesearch.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_singlesearch.svg"); + createOnboardingTourButton(div, + "onboarding-tour-singlesearch-button", "onboarding.tour-singlesearch.button"); + + return div; + }, + }, + "performance": { + id: "onboarding-tour-performance", + instantComplete: true, + tourNameId: "onboarding.tour-performance", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-performance.title"), + message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-performance.message", [BRAND_SHORT_NAME], 1), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win, bundle) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-performance.title", "onboarding.tour-performance.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_performance.svg"); + + return div; + }, + }, + "screenshots": { + id: "onboarding-tour-screenshots", + tourNameId: "onboarding.tour-screenshots", + getNotificationStrings(bundle) { + return { + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-screenshots.title"), + message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-screenshots.message", [BRAND_SHORT_NAME], 1), + button: bundle.GetStringFromName("onboarding.button.learnMore"), + }; + }, + getPage(win, bundle) { + let div = win.document.createElement("div"); + // Screenshot tour opens the screenshot page directly, see below a#onboarding-tour-screenshots-button. + // The screenshots page should be responsible for highlighting the Screenshots button + + createOnboardingTourDescription(div, + "onboarding.tour-screenshots.title", "onboarding.tour-screenshots.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_screenshots.svg"); + + let aside = createOnboardingTourButton(div, + "onboarding-tour-screenshots-button", + "onboarding.tour-screenshots.button", + "a"); + + let button = aside.querySelector("a"); + button.setAttribute("href", "https://screenshots.firefox.com/#tour"); + button.setAttribute("target", "_blank"); + + return div; + }, + }, +}; + +/** + * The script won't be initialized if we turned off onboarding by + * setting "browser.onboarding.enabled" to false. + */ +class Onboarding { + constructor(mm, contentWindow) { + this.mm = mm; + this.init(contentWindow); + } + + + /** + * @param {String} action the action to ask the chrome to do + * @param {Array | Object} params the parameters for the action + */ + sendMessageToChrome(action, params) { + this.mm.sendAsyncMessage("Onboarding:OnContentMessage", { + action, params, + }); + } + + /** + * Template code for talking to `PingCentre` + * @param {Object} data the payload for the telemetry + */ + telemetry(data) { + this.sendMessageToChrome("ping-centre", {data}); + } + + registerNewTelemetrySession(data) { + this.telemetry(Object.assign(data, { + type: "onboarding-register-session", + })); + } + + async init(contentWindow) { + this._window = contentWindow; + // session_key is used for telemetry to track the current tab. + // The number will renew after reloading the page. + this._session_key = Date.now(); + this._tours = []; + this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update"); + + let tourIds = this._getTourIDList(); + tourIds.forEach(tourId => { + if (onboardingTourset[tourId]) { + this._tours.push(onboardingTourset[tourId]); + } + }); + + if (this._tours.length === 0) { + return; + } + + // We want to create and append elements after CSS is loaded so + // no flash of style changes and no additional reflow. + await this._loadCSS(); + this._bundle = Services.strings.createBundle(BUNDLE_URI); + + this._loadJS(UITOUR_JS_URI); + + this.uiInitialized = false; + let doc = this._window.document; + if (doc.hidden) { + // When the preloaded-browser feature is on, + // it would preload a hidden about:newtab in the background. + // We don't want to show onboarding experience in that hidden state. + let onVisible = () => { + if (!doc.hidden) { + doc.removeEventListener("visibilitychange", onVisible); + this._startUI(); + } + }; + doc.addEventListener("visibilitychange", onVisible); + } else { + this._startUI(); + } + } + + _startUI() { + this.registerNewTelemetrySession({ + page: this._window.location.href, + session_key: this._session_key, + tour_type: this._tourType, + }); + + this._window.addEventListener("beforeunload", this); + this._window.addEventListener("unload", this); + this._window.addEventListener("resize", this); + this._resizeTimerId = + this._window.requestIdleCallback(() => this._resizeUI()); + // start log the onboarding-session when the tab is visible + this.telemetry({ + type: "onboarding-session-begin", + session_key: this._session_key, + }); + } + + _resizeUI() { + this._windowWidth = this._window.document.body.getBoundingClientRect().width; + if (this._windowWidth < ONBOARDING_MIN_WIDTH_PX) { + // Don't show the overlay UI before we get to a better, responsive design. + this.destroy(); + return; + } + + this._initUI(); + if (this._isFirstSession && this._windowWidth >= SPEECH_BUBBLE_MIN_WIDTH_PX) { + this._overlayIcon.classList.add("onboarding-speech-bubble"); + } else { + this._overlayIcon.classList.remove("onboarding-speech-bubble"); + } + } + + _initUI() { + if (this.uiInitialized) { + return; + } + this.uiInitialized = true; + this._tourItems = []; + this._tourPages = []; + + let { body } = this._window.document; + this._overlayIcon = this._renderOverlayButton(); + this._overlayIcon.addEventListener("click", this); + this._overlayIcon.addEventListener("keypress", this); + body.insertBefore(this._overlayIcon, body.firstChild); + + this._overlay = this._renderOverlay(); + this._overlay.addEventListener("click", this); + this._overlay.addEventListener("keydown", this); + this._overlay.addEventListener("keypress", this); + body.appendChild(this._overlay); + + this._loadJS(TOUR_AGENT_JS_URI); + + this._initPrefObserver(); + this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT)); + + // Doing tour notification takes some effort. Let's do it on idle. + this._window.requestIdleCallback(() => this.showNotification()); + } + + _getTourIDList() { + let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, ""); + return tours.split(",").filter(tourId => { + if (tourId === "sync" && !Services.prefs.getBoolPref("identity.fxaccounts.enabled")) { + return false; + } + return tourId !== ""; + }).map(tourId => tourId.trim()); + } + + _initPrefObserver() { + if (this._prefsObserved) { + return; + } + + this._prefsObserved = new Map(); + this._prefsObserved.set("browser.onboarding.state", () => { + this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT)); + }); + this._tours.forEach(tour => { + let tourId = tour.id; + this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => { + this.markTourCompletionState(tourId); + this._checkWatermarkByTours(); + }); + }); + for (let [name, callback] of this._prefsObserved) { + Services.prefs.addObserver(name, callback); + } + } + + _checkWatermarkByTours() { + let tourDone = this._tours.every(tour => this.isTourCompleted(tour.id)); + if (tourDone) { + this.sendMessageToChrome("set-prefs", [{ + name: "browser.onboarding.state", + value: ICON_STATE_WATERMARK, + }]); + } + } + + _clearPrefObserver() { + if (this._prefsObserved) { + for (let [name, callback] of this._prefsObserved) { + Services.prefs.removeObserver(name, callback); + } + this._prefsObserved = null; + } + } + + /** + * Find a tour that should be selected. It is either a first tour that was not + * yet complete or the first one in the tab list. + */ + get _firstUncompleteTour() { + return this._tours.find(tour => !this.isTourCompleted(tour.id)) || + this._tours[0]; + } + + /* + * Return currently showing tour navigation item + */ + get _activeTourId() { + // We are doing lazy load so there might be no items. + if (!this._tourItems) { + return ""; + } + + let tourItem = this._tourItems.find(item => item.classList.contains("onboarding-active")); + return tourItem ? tourItem.id : ""; + } + + /** + * Return current logo state as "logo" or "watermark". + */ + get _logoState() { + return this._overlayIcon.classList.contains("onboarding-watermark") ? + "watermark" : "logo"; + } + + /** + * Return current speech bubble state as "bubble", "dot" or "hide". + */ + get _bubbleState() { + let state; + if (this._overlayIcon.classList.contains("onboarding-watermark")) { + state = "hide"; + } else if (this._overlayIcon.classList.contains("onboarding-speech-bubble")) { + state = "bubble"; + } else { + state = "dot"; + } + return state; + } + + /** + * Return current notification state as "show", "hide" or "finished". + */ + get _notificationState() { + if (this._notificationCachedState === "finished") { + return this._notificationCachedState; + } + + if (Services.prefs.getBoolPref(NOTIFICATION_FINISHED_PREF, false)) { + this._notificationCachedState = "finished"; + } else if (this._notification) { + this._notificationCachedState = "show"; + } else { + // we know it is in the hidden state if there's no notification bar + this._notificationCachedState = "hide"; + } + + return this._notificationCachedState; + } + + /** + * Return current notification prompt count. + */ + get _notificationPromptCount() { + return Services.prefs.getIntPref(PROMPT_COUNT_PREF, 0); + } + + /** + * Return current screen width and round it up to the nearest 50 pixels. + * Collecting rounded values reduces the risk that this could be used to + * derive a unique user identifier + */ + get _windowWidthRounded() { + return Math.round(this._windowWidth / 50) * 50; + } + + handleClick(target) { + let { id, classList } = target; + // Only containers receive pointer events in onboarding tour tab list, + // actual semantic tab is their first child. + if (classList.contains("onboarding-tour-item-container")) { + ({ id, classList } = target.firstChild); + } + + switch (id) { + case "onboarding-overlay-button-icon": + case "onboarding-overlay-button": + this.telemetry({ + type: "onboarding-logo-click", + bubble_state: this._bubbleState, + logo_state: this._logoState, + notification_state: this._notificationState, + session_key: this._session_key, + width: this._windowWidthRounded, + }); + this.showOverlay(); + this.gotoPage(this._firstUncompleteTour.id); + break; + case "onboarding-skip-tour-button": + this.hideNotification(); + this.hideOverlay(); + this.skipTour(); + break; + case "onboarding-overlay-close-btn": + // If the clicking target is directly on the outer-most overlay, + // that means clicking outside the tour content area. + // Let's toggle the overlay. + case "onboarding-overlay": + let eventName = id === "onboarding-overlay-close-btn" ? + "overlay-close-button-click" : "overlay-close-outside-click"; + this.telemetry({ + type: eventName, + current_tour_id: this._activeTourId, + session_key: this._session_key, + target_tour_id: this._activeTourId, + width: this._windowWidthRounded, + }); + this.hideOverlay(); + break; + case "onboarding-notification-close-btn": + let currentTourId = this._notificationBar.dataset.targetTourId; + // should trigger before notification-session event is sent + this.telemetry({ + type: "notification-close-button-click", + bubble_state: this._bubbleState, + current_tour_id: currentTourId, + logo_state: this._logoState, + notification_impression: this._notificationPromptCount, + notification_state: this._notificationState, + session_key: this._session_key, + target_tour_id: currentTourId, + width: this._windowWidthRounded, + }); + this.hideNotification(); + this._removeTourFromNotificationQueue(currentTourId); + break; + case "onboarding-notification-action-btn": + let tourId = this._notificationBar.dataset.targetTourId; + this.telemetry({ + type: "notification-cta-click", + bubble_state: this._bubbleState, + current_tour_id: tourId, + logo_state: this._logoState, + notification_impression: this._notificationPromptCount, + notification_state: this._notificationState, + session_key: this._session_key, + target_tour_id: tourId, + width: this._windowWidthRounded, + }); + this.showOverlay(); + this.gotoPage(tourId); + this._removeTourFromNotificationQueue(tourId); + break; + } + if (classList.contains("onboarding-tour-item")) { + this.telemetry({ + type: "overlay-nav-click", + current_tour_id: this._activeTourId, + session_key: this._session_key, + target_tour_id: id, + width: this._windowWidthRounded, + }); + this.gotoPage(id); + // Keep focus (not visible) on current item for potential keyboard + // navigation. + target.focus(); + } else if (classList.contains("onboarding-tour-action-button")) { + let activeTourId = this._activeTourId; + this.setToursCompleted([ activeTourId ]); + this.telemetry({ + type: "overlay-cta-click", + current_tour_id: activeTourId, + session_key: this._session_key, + target_tour_id: activeTourId, + width: this._windowWidthRounded, + }); + } + } + + /** + * Wrap keyboard focus within the dialog. + * When moving forward, focus on the first element when the current focused + * element is the last one. + * When moving backward, focus on the last element when the current focused + * element is the first one. + * Do nothing if focus is moving in the middle of the list of dialog's focusable + * elements. + * + * @param {DOMNode} current currently focused element + * @param {Boolean} back direction + * @return {DOMNode} newly focused element if any + */ + wrapMoveFocus(current, back) { + let elms = [...this._dialog.querySelectorAll( + `button, input[type="checkbox"], input[type="email"], [tabindex="0"]`)]; + let next; + if (back) { + if (elms.indexOf(current) === 0) { + next = elms[elms.length - 1]; + next.focus(); + } + } else if (elms.indexOf(current) === elms.length - 1) { + next = elms[0]; + next.focus(); + } + return next; + } + + handleKeydown(event) { + let { target, key, shiftKey } = event; + + // Currently focused item could be tab container if previous navigation was done + // via mouse. + if (target.classList.contains("onboarding-tour-item-container")) { + target = target.firstChild; + } + let targetIndex; + switch (key) { + case "ArrowUp": + // Go to and focus on the previous tab if it's available. + targetIndex = this._tourItems.indexOf(target); + if (targetIndex > 0) { + let previous = this._tourItems[targetIndex - 1]; + this.handleClick(previous); + previous.focus(); + } + event.preventDefault(); + break; + case "ArrowDown": + // Go to and focus on the next tab if it's available. + targetIndex = this._tourItems.indexOf(target); + if (targetIndex > -1 && targetIndex < this._tourItems.length - 1) { + let next = this._tourItems[targetIndex + 1]; + this.handleClick(next); + next.focus(); + } + event.preventDefault(); + break; + case "Escape": + this.hideOverlay(); + break; + case "Tab": + let next = this.wrapMoveFocus(target, shiftKey); + // If focus was wrapped, prevent Tab key default action. + if (next) { + event.preventDefault(); + } + break; + default: + break; + } + event.stopPropagation(); + } + + handleKeypress(event) { + let { target, key } = event; + + if (target === this._overlayIcon) { + if ([" ", "Enter"].includes(key)) { + // Remember that the dialog was opened with a keyboard. + this._overlayIcon.dataset.keyboardFocus = true; + this.handleClick(target); + event.preventDefault(); + } + return; + } + + // Currently focused item could be tab container if previous navigation was done + // via mouse. + if (target.classList.contains("onboarding-tour-item-container")) { + target = target.firstChild; + } + switch (key) { + case " ": + case "Enter": + // Assume that the handle function should be identical for keyboard + // activation if there is a click handler for the target. + if (target.classList.contains("onboarding-tour-item")) { + this.handleClick(target); + target.focus(); + } + break; + default: + break; + } + event.stopPropagation(); + } + + handleEvent(evt) { + switch (evt.type) { + case "beforeunload": + // To make sure the telemetry pings are sent, + // we send "onboarding-session-end" ping as well as + // "overlay-session-end" and "notification-session-end" ping + // (by hiding the overlay and notificaiton) on beforeunload. + this.hideOverlay(); + this.hideNotification(); + this.telemetry({ + type: "onboarding-session-end", + session_key: this._session_key, + }); + break; + case "unload": + // Notice: Cannot do `destroy` on beforeunload, must do on unload. + // Otherwise, we would hit the docShell leak in the test. + // See Bug 1413830#c190 and Bug 1429652 for details. + this.destroy(); + break; + case "resize": + this._window.cancelIdleCallback(this._resizeTimerId); + this._resizeTimerId = + this._window.requestIdleCallback(() => this._resizeUI()); + break; + case "keydown": + this.handleKeydown(evt); + break; + case "keypress": + this.handleKeypress(evt); + break; + case "click": + this.handleClick(evt.target); + break; + default: + break; + } + } + + destroy() { + if (!this.uiInitialized) { + return; + } + this.uiInitialized = false; + + this._overlayIcon.dispatchEvent(new this._window.CustomEvent("Agent:Destroy")); + + this._clearPrefObserver(); + this._overlayIcon.remove(); + if (this._overlay) { + // send overlay-session telemetry + this.hideOverlay(); + this._overlay.remove(); + } + if (this._notificationBar) { + // send notification-session telemetry + this.hideNotification(); + this._notificationBar.remove(); + } + this._tourItems = this._tourPages = + this._overlayIcon = this._overlay = this._notificationBar = null; + } + + _onIconStateChange(state) { + switch (state) { + case ICON_STATE_DEFAULT: + this._overlayIcon.classList.remove("onboarding-watermark"); + break; + case ICON_STATE_WATERMARK: + this._overlayIcon.classList.add("onboarding-watermark"); + break; + } + return true; + } + + showOverlay() { + if (this._tourItems.length == 0) { + // Lazy loading until first toggle. + this._loadTours(this._tours); + } + + if (this._overlay && !this._overlay.classList.contains("onboarding-opened")) { + this.hideNotification(); + this._overlay.classList.add("onboarding-opened"); + this.toggleModal(true); + this.telemetry({ + type: "overlay-session-begin", + session_key: this._session_key, + }); + } + } + + hideOverlay() { + if (this._overlay && this._overlay.classList.contains("onboarding-opened")) { + this._overlay.classList.remove("onboarding-opened"); + this.toggleModal(false); + this.telemetry({ + type: "overlay-session-end", + session_key: this._session_key, + }); + } + } + + /** + * Set modal dialog state and properties for accessibility purposes. + * @param {Boolean} opened whether the dialog is opened or closed. + */ + toggleModal(opened) { + let { document: doc } = this._window; + if (opened) { + // Set aria-hidden to true for the rest of the document. + [...doc.body.children].forEach( + child => child.id !== "onboarding-overlay" && + child.setAttribute("aria-hidden", true)); + // When dialog is opened with the keyboard, focus on the first + // uncomplete tour because it will be the selected tour. + if (this._overlayIcon.dataset.keyboardFocus) { + doc.getElementById(this._firstUncompleteTour.id).focus(); + } else { + // When the dialog is opened with the mouse, focus on the dialog + // itself to avoid visible keyboard focus styling. + this._dialog.focus(); + } + } else { + // Remove all set aria-hidden attributes. + [...doc.body.children].forEach( + child => child.removeAttribute("aria-hidden")); + // If dialog was opened with a keyboard, set the focus back to the overlay + // button. + if (this._overlayIcon.dataset.keyboardFocus) { + delete this._overlayIcon.dataset.keyboardFocus; + this._overlayIcon.focus(); + } else { + this._window.document.activeElement.blur(); + } + } + } + + /** + * Switch to proper tour. + * @param {String} tourId specify which tour should be switched. + */ + gotoPage(tourId) { + let targetPageId = `${tourId}-page`; + for (let page of this._tourPages) { + if (page.id === targetPageId) { + page.style.display = ""; + page.dispatchEvent(new this._window.CustomEvent("beforeshow")); + } else { + page.style.display = "none"; + } + } + for (let tab of this._tourItems) { + if (tab.id == tourId) { + tab.classList.add("onboarding-active"); + tab.setAttribute("aria-selected", true); + this.telemetry({ + type: "overlay-current-tour", + current_tour_id: tourId, + session_key: this._session_key, + width: this._windowWidthRounded, + }); + + // Some tours should complete instantly upon showing. + if (tab.getAttribute("data-instant-complete")) { + this.setToursCompleted([tourId]); + } + } else { + tab.classList.remove("onboarding-active"); + tab.setAttribute("aria-selected", false); + } + } + } + + isTourCompleted(tourId) { + return Services.prefs.getBoolPref(`browser.onboarding.tour.${tourId}.completed`, false); + } + + setToursCompleted(tourIds) { + let params = []; + tourIds.forEach(id => { + if (!this.isTourCompleted(id)) { + params.push({ + name: `browser.onboarding.tour.${id}.completed`, + value: true, + }); + } + }); + if (params.length > 0) { + this.sendMessageToChrome("set-prefs", params); + } + } + + markTourCompletionState(tourId) { + // We are doing lazy load so there might be no items. + if (!this._tourItems || this._tourItems.length === 0) { + return; + } + + let completed = this.isTourCompleted(tourId); + let targetItem = this._tourItems.find(item => item.id == tourId); + let completedTextId = `onboarding-complete-${tourId}-text`; + // Accessibility: Text version of the auxiliary information about the tour + // item completion is provided via an invisible node with an aria-label that + // the tab is pointing to via aria-described by. + let completedText = targetItem.querySelector(`#${completedTextId}`); + if (completed) { + targetItem.classList.add("onboarding-complete"); + if (!completedText) { + completedText = this._window.document.createElement("span"); + completedText.id = completedTextId; + completedText.setAttribute("aria-label", + this._bundle.GetStringFromName("onboarding.complete")); + targetItem.appendChild(completedText); + targetItem.setAttribute("aria-describedby", completedTextId); + } + } else { + targetItem.classList.remove("onboarding-complete"); + targetItem.removeAttribute("aria-describedby"); + if (completedText) { + completedText.remove(); + } + } + } + + get _isFirstSession() { + // Should only directly return on the "false" case. Consider: + // 1. On the 1st session, `_firstSession` is true + // 2. During the 1st session, user resizes window so that the UI is destroyed + // 3. After the 1st mute session, user resizes window so that the UI is re-init + if (this._firstSession === false) { + return false; + } + this._firstSession = true; + + // There is a queue, which means we had prompted tour notifications before. Therefore this is not the 1st session. + if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) { + this._firstSession = false; + } + + // When this is set to 0 on purpose, always judge as not the 1st session + if (Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms") === 0) { + this._firstSession = false; + } + + return this._firstSession; + } + + _getLastTourChangeTime() { + return 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0); + } + + _muteNotificationOnFirstSession(lastTourChangeTime) { + if (!this._isFirstSession) { + return false; + } + + if (lastTourChangeTime <= 0) { + this.sendMessageToChrome("set-prefs", [{ + name: "browser.onboarding.notification.last-time-of-changing-tour-sec", + value: Math.floor(Date.now() / 1000), + }]); + return true; + } + let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms"); + return Date.now() - lastTourChangeTime <= muteDuration; + } + + _isTimeForNextTourNotification(lastTourChangeTime) { + let maxCount = Services.prefs.getIntPref("browser.onboarding.notification.max-prompt-count-per-tour"); + if (this._notificationPromptCount >= maxCount) { + return true; + } + + let maxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-per-tour-ms"); + if (lastTourChangeTime && Date.now() - lastTourChangeTime >= maxTime) { + return true; + } + + return false; + } + + _removeTourFromNotificationQueue(tourId) { + let params = []; + let queue = this._getNotificationQueue(); + params.push({ + name: "browser.onboarding.notification.tour-ids-queue", + value: queue.filter(id => id != tourId).join(","), + }); + params.push({ + name: "browser.onboarding.notification.last-time-of-changing-tour-sec", + value: 0, + }); + params.push({ + name: "browser.onboarding.notification.prompt-count", + value: 0, + }); + this.sendMessageToChrome("set-prefs", params); + } + + _getNotificationQueue() { + let queue = ""; + if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) { + queue = Services.prefs.getStringPref("browser.onboarding.notification.tour-ids-queue"); + } else { + // For each tour, it only gets 2 chances to prompt with notification + // (each chance includes 8 impressions or 5-days max life time) + // if user never interact with it. + // Assume there are tour #0 ~ #5. Here would form the queue as + // "#0,#1,#2,#3,#4,#5,#0,#1,#2,#3,#4,#5". + // Then we would loop through this queue and remove prompted tour from the queue + // until the queue is empty. + let ids = this._tours.map(tour => tour.id).join(","); + queue = `${ids},${ids}`; + this.sendMessageToChrome("set-prefs", [{ + name: "browser.onboarding.notification.tour-ids-queue", + value: queue, + }]); + } + return queue ? queue.split(",") : []; + } + + showNotification() { + if (this._notificationState === "finished") { + return; + } + + let lastTime = this._getLastTourChangeTime(); + if (this._muteNotificationOnFirstSession(lastTime)) { + return; + } + + // After the notification mute on the 1st session, + // we don't want to show the speech bubble by default + this._overlayIcon.classList.remove("onboarding-speech-bubble"); + + let queue = this._getNotificationQueue(); + let totalMaxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-all-tours-ms"); + if (lastTime && Date.now() - lastTime >= totalMaxTime) { + // Reach total max life time for all tour notifications. + // Clear the queue so that we would finish tour notifications below + queue = []; + } + + let startQueueLength = queue.length; + // See if need to move on to the next tour + if (queue.length > 0 && this._isTimeForNextTourNotification(lastTime)) { + queue.shift(); + } + // We don't want to prompt the completed tour. + while (queue.length > 0 && this.isTourCompleted(queue[0])) { + queue.shift(); + } + + if (queue.length == 0) { + this.sendMessageToChrome("set-prefs", [ + { + name: NOTIFICATION_FINISHED_PREF, + value: true, + }, + { + name: "browser.onboarding.notification.tour-ids-queue", + value: "", + }, + { + name: "browser.onboarding.state", + value: ICON_STATE_WATERMARK, + }, + ]); + return; + } + let targetTourId = queue[0]; + let targetTour = this._tours.find(tour => tour.id == targetTourId); + + // Show the target tour notification + this._notificationBar = this._renderNotificationBar(); + this._notificationBar.addEventListener("click", this); + this._notificationBar.dataset.targetTourId = targetTour.id; + let notificationStrings = targetTour.getNotificationStrings(this._bundle); + let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn"); + actionBtn.textContent = notificationStrings.button; + let tourTitle = this._notificationBar.querySelector("#onboarding-notification-tour-title"); + tourTitle.textContent = notificationStrings.title; + let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message"); + tourMessage.textContent = notificationStrings.message; + this._notificationBar.classList.add("onboarding-opened"); + this._window.document.body.appendChild(this._notificationBar); + + let params = []; + let promptCount = 1; + if (startQueueLength != queue.length) { + // We just change tour so update the time, the count and the queue + params.push({ + name: "browser.onboarding.notification.last-time-of-changing-tour-sec", + value: Math.floor(Date.now() / 1000), + }); + params.push({ + name: PROMPT_COUNT_PREF, + value: promptCount, + }); + params.push({ + name: "browser.onboarding.notification.tour-ids-queue", + value: queue.join(","), + }); + } else { + promptCount = this._notificationPromptCount + 1; + params.push({ + name: PROMPT_COUNT_PREF, + value: promptCount, + }); + } + this.sendMessageToChrome("set-prefs", params); + this.telemetry({ + type: "notification-session-begin", + session_key: this._session_key, + }); + // since set-perfs is async, pass promptCount directly to avoid gathering the wrong + // notification_impression. + this.telemetry({ + type: "notification-appear", + bubble_state: this._bubbleState, + current_tour_id: targetTourId, + logo_state: this._logoState, + notification_impression: promptCount, + notification_state: this._notificationState, + session_key: this._session_key, + width: this._windowWidthRounded, + }); + } + + hideNotification() { + if (this._notificationBar) { + if (this._notificationBar.classList.contains("onboarding-opened")) { + this._notificationBar.classList.remove("onboarding-opened"); + this.telemetry({ + type: "notification-session-end", + session_key: this._session_key, + }); + } + } + } + + _renderNotificationBar() { + let footer = this._window.document.createElement("footer"); + footer.id = "onboarding-notification-bar"; + footer.setAttribute("aria-live", "polite"); + footer.setAttribute("aria-labelledby", "onboarding-notification-tour-title"); + + let section = this._window.document.createElement("section"); + section.id = "onboarding-notification-message-section"; + section.setAttribute("role", "presentation"); + footer.appendChild(section); + + let icon = this._window.document.createElement("div"); + icon.id = "onboarding-notification-tour-icon"; + icon.setAttribute("role", "presentation"); + section.appendChild(icon); + + let onboardingNotificationBody = this._window.document.createElement("div"); + onboardingNotificationBody.id = "onboarding-notification-body"; + onboardingNotificationBody.setAttribute("role", "presentation"); + section.appendChild(onboardingNotificationBody); + + let title = this._window.document.createElement("h1"); + title.id = "onboarding-notification-tour-title"; + onboardingNotificationBody.appendChild(title); + + let message = this._window.document.createElement("p"); + message.id = "onboarding-notification-tour-message"; + onboardingNotificationBody.appendChild(message); + + let actionButton = this._window.document.createElement("button"); + actionButton.id = "onboarding-notification-action-btn"; + actionButton.className = "onboarding-action-button"; + section.appendChild(actionButton); + + let closeButton = this._window.document.createElement("button"); + closeButton.id = "onboarding-notification-close-btn"; + closeButton.className = "onboarding-close-btn"; + footer.appendChild(closeButton); + + closeButton.setAttribute("title", + this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip")); + + return footer; + } + + skipTour() { + this.setToursCompleted(this._tours.map(tour => tour.id)); + this.sendMessageToChrome("set-prefs", [ + { + name: NOTIFICATION_FINISHED_PREF, + value: true, + }, + { + name: "browser.onboarding.state", + value: ICON_STATE_WATERMARK, + }, + ]); + this.telemetry({ + type: "overlay-skip-tour", + current_tour_id: this._activeTourId, + session_key: this._session_key, + width: this._windowWidthRounded, + }); + } + + _renderOverlay() { + let div = this._window.document.createElement("div"); + div.id = "onboarding-overlay"; + + this._dialog = this._window.document.createElement("div"); + this._dialog.setAttribute("role", "dialog"); + this._dialog.setAttribute("tabindex", "-1"); + this._dialog.setAttribute("aria-labelledby", "onboarding-header"); + this._dialog.id = ONBOARDING_DIALOG_ID; + div.appendChild(this._dialog); + + let header = this._window.document.createElement("header"); + header.id = "onboarding-header"; + header.textContent = this._bundle.GetStringFromName("onboarding.overlay-title2"); + this._dialog.appendChild(header); + + let nav = this._window.document.createElement("nav"); + this._dialog.appendChild(nav); + + let tourList = this._window.document.createElement("ul"); + tourList.id = "onboarding-tour-list"; + tourList.setAttribute("role", "tablist"); + nav.appendChild(tourList); + + let footer = this._window.document.createElement("footer"); + footer.id = "onboarding-footer"; + this._dialog.appendChild(footer); + + let button = this._window.document.createElement("button"); + button.id = "onboarding-overlay-close-btn"; + button.className = "onboarding-close-btn"; + button.setAttribute("title", + this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip")); + this._dialog.appendChild(button); + + // support show/hide skip tour button via pref + if (!Services.prefs.getBoolPref("browser.onboarding.skip-tour-button.hide", false)) { + let skipButton = this._window.document.createElement("button"); + skipButton.id = "onboarding-skip-tour-button"; + skipButton.classList.add("onboarding-action-button"); + skipButton.textContent = this._bundle.GetStringFromName("onboarding.skip-tour-button-label"); + footer.appendChild(skipButton); + } + + return div; + } + + _renderOverlayButton() { + let button = this._window.document.createElement("button"); + // support customize speech bubble string via pref + let tooltipStringPrefId = ""; + let defaultTourStringId = ""; + if (this._tourType === "new") { + tooltipStringPrefId = "browser.onboarding.newtour.tooltip"; + defaultTourStringId = SPEECH_BUBBLE_NEWTOUR_STRING_ID; + } else { + tooltipStringPrefId = "browser.onboarding.updatetour.tooltip"; + defaultTourStringId = SPEECH_BUBBLE_UPDATETOUR_STRING_ID; + } + let tooltip = ""; + try { + let tooltipStringId = Services.prefs.getStringPref(tooltipStringPrefId, defaultTourStringId); + tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1); + } catch (e) { + Cu.reportError(e); + // fallback to defaultTourStringId to proceed + tooltip = this._bundle.formatStringFromName(defaultTourStringId, [BRAND_SHORT_NAME], 1); + } + button.setAttribute("aria-label", tooltip); + button.id = "onboarding-overlay-button"; + button.setAttribute("aria-haspopup", true); + button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`); + let defaultImg = this._window.document.createElement("img"); + defaultImg.id = "onboarding-overlay-button-icon"; + defaultImg.setAttribute("role", "presentation"); + defaultImg.src = Services.prefs.getStringPref("browser.onboarding.default-icon-src", + "chrome://branding/content/icon64.png"); + button.appendChild(defaultImg); + let watermarkImg = this._window.document.createElement("img"); + watermarkImg.id = "onboarding-overlay-button-watermark-icon"; + watermarkImg.setAttribute("role", "presentation"); + watermarkImg.src = Services.prefs.getStringPref("browser.onboarding.watermark-icon-src", + "resource://onboarding/img/watermark.svg"); + button.appendChild(watermarkImg); + return button; + } + + _loadTours(tours) { + let itemsFrag = this._window.document.createDocumentFragment(); + let pagesFrag = this._window.document.createDocumentFragment(); + for (let tour of tours) { + // Create tour navigation items dynamically + let li = this._window.document.createElement("li"); + // List item should have no semantics. It is just a container for an + // actual tab. + li.setAttribute("role", "presentation"); + li.className = "onboarding-tour-item-container"; + // Focusable but not tabbable. + li.tabIndex = -1; + + let tab = this._window.document.createElement("span"); + tab.id = tour.id; + tab.textContent = this._bundle.GetStringFromName(tour.tourNameId); + tab.className = "onboarding-tour-item"; + if (tour.instantComplete) { + tab.dataset.instantComplete = true; + } + tab.tabIndex = 0; + tab.setAttribute("role", "tab"); + + let tourPanelId = `${tour.id}-page`; + tab.setAttribute("aria-controls", tourPanelId); + + li.appendChild(tab); + itemsFrag.appendChild(li); + // Dynamically create tour pages + let div = tour.getPage.call(this, this._window, this._bundle); + + // Do a traverse for elements in the page that need to be localized. + let l10nElements = div.querySelectorAll("[data-l10n-id]"); + for (let i = 0; i < l10nElements.length; i++) { + let element = l10nElements[i]; + // We always put brand short name as the first argument for it's the + // only and frequently used arguments in our l10n case. Rewrite it if + // other arguments appear. + element.textContent = this._bundle.formatStringFromName( + element.dataset.l10nId, [BRAND_SHORT_NAME], 1); + } + + div.id = tourPanelId; + div.classList.add("onboarding-tour-page"); + div.setAttribute("role", "tabpanel"); + div.setAttribute("aria-labelledby", tour.id); + div.style.display = "none"; + pagesFrag.appendChild(div); + // Cache elements in arrays for later use to avoid cost of querying elements + this._tourItems.push(tab); + this._tourPages.push(div); + + this.markTourCompletionState(tour.id); + } + + let ul = this._window.document.getElementById("onboarding-tour-list"); + ul.appendChild(itemsFrag); + let footer = this._window.document.getElementById("onboarding-footer"); + this._dialog.insertBefore(pagesFrag, footer); + } + + _loadCSS() { + // Returning a Promise so we can inform caller of loading complete + // by resolving it. + return new Promise(resolve => { + let doc = this._window.document; + let link = doc.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = ONBOARDING_CSS_URL; + link.addEventListener("load", resolve); + doc.head.appendChild(link); + }); + } + + _loadJS(uri) { + let doc = this._window.document; + let script = doc.createElement("script"); + script.type = "text/javascript"; + script.src = uri; + doc.head.appendChild(script); + } +} diff --git a/browser/extensions/onboarding/content/img/figure_addons.svg b/browser/extensions/onboarding/content/img/figure_addons.svg new file mode 100644 index 0000000000000..b5f056737f118 --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_addons.svg @@ -0,0 +1 @@ +<svg width="295" height="199" viewBox="0 0 295 199" xmlns="http://www.w3.org/2000/svg"><title>addons</title><defs><linearGradient x1="-3335.765%" y1="-2236.632%" x2="5558.543%" y2="3780.103%" id="a"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradient x1="-251.09%" y1="-799.657%" x2="413.095%" y2="1054.368%" id="b"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradien [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_customize.svg b/browser/extensions/onboarding/content/img/figure_customize.svg new file mode 100644 index 0000000000000..0c0cb30df5dc7 --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_customize.svg @@ -0,0 +1,561 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="295" height="238"> + <defs> + <linearGradient id="a" x1="-678.179817%" x2="218.03211%" y1="-1879.5122%" y2="503.09878%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="-2438.15968%" x2="713.035484%" y1="-2346.83281%" y2="705.8875%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="c" x1="-1876.47349%" x2="477.431325%" y1="-2215.7169%" y2="536.030986%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="d" x1="-300.502319%" x2="326.878731%" y1="-277.869139%" y2="301.876261%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="e" x1="-556.386842%" x2="471.897895%" y1="-1050.94952%" y2="809.757143%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="f" x1="-2301.11875%" x2="1769.175%" y1="-4460.38%" y2="3354.584%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="g" x1="-14090.38%" x2="5447.03%" y1="-14085.94%" y2="5451.47%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="h" x1="-1245.88053%" x2="483.093805%" y1="-2962.82857%" y2="1024.39796%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="i" x1="-4762.32308%" x2="1072.27051%" y1="-2525.31233%" y2="591.799315%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="j" x1="-419.785061%" x2="175.867683%" y1="-263.047589%" y2="146.541719%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="k" x1="-13945.16%" x2="5592.25%" y1="-13931.16%" y2="5606.26%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="l" x1="-93.8791876%" x2="171.036409%" y1="-368.29%" y2="383.149231%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="m" x1="-105.119971%" x2="175.589943%" y1="-106.702736%" y2="160.566895%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="n" x1="-4526.45652%" x2="3968.06957%" y1="-3864.98889%" y2="3371.08889%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="o" x1="-1590.58053%" x2="2387.43252%" y1="-835.835705%" y2="1325.72397%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="p" x1="-1174.27536%" x2="1657.23333%" y1="-1275.87873%" y2="1781.26242%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="q" x1="-8557.56%" x2="10979.85%" y1="-4234.38%" y2="5534.325%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="r" x1="-949.737079%" x2="1245.47865%" y1="-1023.81277%" y2="1336.75514%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="s" x1="-850.555238%" x2="1010.15048%" y1="-759.279881%" y2="912.10717%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="t" x1="-2526.775%" x2="962.048214%" y1="-2513.94763%" y2="949.261152%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="u" x1="-953.117868%" x2="406.88755%" y1="-1083.71008%" y2="471.112383%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="v" x1="-1736.94827%" x2="671.463404%" y1="-2238.58822%" y2="855.656147%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="w" x1="-9592.54%" x2="9944.87%" y1="-9613.77%" y2="9923.64%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="x" x1="-546.9251%" x2="669.232184%" y1="-637.97868%" y2="716.339388%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="y" x1="-2626.25%" x2="2515.17368%" y1="-10166.57%" y2="9370.85%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="z" x1="-26076.58%" x2="9092.02%" y1="-26064.58%" y2="9104.02%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="A" x1="-11996.8348%" x2="3293.86087%" y1="-4084.84179%" y2="1164.20299%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="B" x1="-1988.44219%" x2="759.104687%" y1="-1576.81875%" y2="621.219375%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="C" x1="-4889.30185%" x2="1623.40185%" y1="-2351.25495%" y2="817.087387%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="D" x1="-2655.5559%" x2="951.48%" y1="-6714.61282%" y2="2302.97692%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="E" x1="-11418.996%" x2="2648.448%" y1="-28603.67%" y2="6564.93%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="F" x1="-1067.54883%" x2="792.163033%" y1="-899.682353%" y2="691.657014%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="G" x1="-3245.82558%" x2="2272.05861%" y1="-2753.32267%" y2="1935.824%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="H" x1="-835.133806%" x2="827.684161%" y1="-835.133806%" y2="827.684161%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="I" x1="-4541.82131%" x2="1223.52295%" y1="-2322.54576%" y2="657.84322%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="J" x1="-2057.47051%" x2="889.742903%" y1="-1738.77914%" y2="791.335971%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="K" x1="-1278.62667%" x2="1189.34526%" y1="-1278.9986%" y2="1188.97333%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="L" x1="-6112.0075%" x2="2680.1425%" y1="-6270.03333%" y2="2747.55641%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="M" x1="-1115.93023%" x2="572.391158%" y1="-1175.6355%" y2="582.7945%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="N" x1="-9656.07586%" x2="2471.02759%" y1="-9322.84667%" y2="2400.02%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="O" x1="-7887.73698%" x2="3321.17237%" y1="-6188.2325%" y2="2603.9175%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="P" x1="-984.783738%" x2="288.77261%" y1="-1902.68288%" y2="506.125342%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="Q" x1="-2522.67732%" x2="1102.95155%" y1="-5039.01837%" y2="2138.24694%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="R" x1="-5921.7225%" x2="2870.4275%" y1="-6075.45385%" y2="2942.1359%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="S" x1="-5881.53%" x2="2910.62%" y1="-5881.26%" y2="2910.89%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="T" x1="-5841.3375%" x2="2950.8125%" y1="-5841.4525%" y2="2950.6975%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="U" x1="-7423.23691%" x2="3785.67244%" y1="-5801.6425%" y2="2990.5075%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="V" x1="-4020.34%" x2="1003.74571%" y1="-2527.16182%" y2="669.983636%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="W" x1="-4517.96032%" x2="1064.35714%" y1="-5480.38654%" y2="1282.80577%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="X" x1="-3834.66828%" x2="2163.11753%" y1="-3992.49299%" y2="2248.99581%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="Y" x1="-132.800878%" x2="141.123835%" y1="-126.933901%" y2="145.268963%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="Z" x1="-8624.4%" x2="10913.01%" y1="-4751.06111%" y2="6103.05556%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="aa" x1="-20576.83%" x2="14591.77%" y1="-11391.2944%" y2="8146.81667%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ab" x1="-3210.85073%" x2="1716.38147%" y1="-3721.57455%" y2="1963.19067%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ac" x1="-964.539164%" x2="305.324758%" y1="-1877.16986%" y2="531.638356%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ad" x1="-5971.9075%" x2="2820.24%" y1="-7463.6%" y2="3526.5875%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ae" x1="-3626.20024%" x2="2128.73795%" y1="-3780.54791%" y2="2217.23789%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="af" x1="-3545.17742%" x2="2127.17742%" y1="-3793.28448%" y2="2270.26724%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ag" x1="-8571.16538%" x2="4955.21923%" y1="-4812.20217%" y2="2833.14565%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ah" x1="-921.592388%" x2="295.314187%" y1="-948.070803%" y2="335.454745%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ai" x1="-1521.4596%" x2="706.721231%" y1="-1247.46875%" y2="591.922626%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aj" x1="-678.258824%" x2="423.307164%" y1="-682.475952%" y2="429.068947%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ak" x1="-6036.96%" x2="2755.19%" y1="-6038.3275%" y2="2753.82%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="al" x1="-876.033667%" x2="359.821607%" y1="-805.490909%" y2="336.346753%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="am" x1="-6523.57663%" x2="4813.74946%" y1="-5038.58141%" y2="3749.13318%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="an" x1="-2645.94937%" x2="963.166315%" y1="-6683.46667%" y2="2334.12564%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ao" x1="-6631.98345%" x2="4705.34265%" y1="-5121.96932%" y2="3665.74527%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ap" x1="-1435.66843%" x2="1068.42563%" y1="-2846.04456%" y2="2010.54343%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aq" x1="-2633.78646%" x2="975.329221%" y1="-6654.88205%" y2="2362.70769%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ar" x1="-2206.3925%" x2="2189.6825%" y1="-2444.83034%" y2="2406.01103%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="as" x1="-5385.00363%" x2="1874.66412%" y1="-10484.884%" y2="3582.556%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="at" x1="-2391.91311%" x2="1397.1783%" y1="-5593.4125%" y2="3198.7375%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="au" x1="-2264.71662%" x2="1521.15732%" y1="-5306.3925%" y2="3485.7575%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="av" x1="-8124.26538%" x2="5402.11923%" y1="-4560.45%" y2="3084.89783%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aw" x1="-651.882139%" x2="479.56521%" y1="-1403.71323%" y2="934.962067%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ax" x1="-782.651586%" x2="579.099454%" y1="-1688.18577%" y2="1133.37245%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="ay" x1="-2808.00445%" x2="930.963547%" y1="-4874.39455%" y2="1519.89636%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="az" x1="-3080.27111%" x2="827.351111%" y1="-4651.45333%" y2="1209.98%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aA" x1="-17842.03%" x2="17326.57%" y1="-17824.13%" y2="17344.47%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aB" x1="-4927.80617%" x2="7466.4141%" y1="-2177.67416%" y2="3371.61183%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aC" x1="-20583.89%" x2="14584.71%" y1="-5842.07714%" y2="4206.09429%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aD" x1="-13953.96%" x2="21214.64%" y1="-2172.57143%" y2="3409.74603%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aE" x1="-13796.3%" x2="21372.3%" y1="-1986.00882%" y2="3185.84412%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aF" x1="-13888.17%" x2="21280.43%" y1="-2353.96379%" y2="3709.58793%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aG" x1="-9372.00909%" x2="6613.71818%" y1="-2958.36812%" y2="2138.53043%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aH" x1="-16384.5222%" x2="12067.4729%" y1="-4573.9%" y2="3418.96364%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aI" x1="-17462.5%" x2="5983.23333%" y1="-13777.5842%" y2="4732.21053%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aJ" x1="-7480.69%" x2="7500.95%" y1="-7483.33%" y2="7498.32%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aK" x1="-7021.27187%" x2="3968.91562%" y1="-20520.9909%" y2="11450.4636%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aL" x1="-9826.0913%" x2="5464.60435%" y1="-22671.15%" y2="12497.45%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aM" x1="-2964.13075%" x2="2873.3758%" y1="-3993.57709%" y2="3854.15587%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aN" x1="-2330.22879%" x2="2205.28384%" y1="-2914.60952%" y2="2667.70794%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aO" x1="-1407.98283%" x2="1424.97017%" y1="-1728.51863%" y2="1719.38333%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aP" x1="-1807.9102%" x2="1780.72245%" y1="-2740.56%" y2="2669.99385%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aQ" x1="-1472.82%" x2="1783.415%" y1="-4365.0426%" y2="5068.41814%"> + <stop stop-color="#FFFBCC" offset="0%"/> + <stop stop-color="#FFC9D5" offset="100%"/> + </linearGradient> + <linearGradient id="aR" x1="-511.087979%" x2="436.292949%" y1="-431.133333%" y2="359.905%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + <linearGradient id="aS" x1="-2336.83483%" x2="1396.15506%" y1="-7055.5%" y2="4019.03333%"> + <stop stop-color="#FFE900" offset="18.75%"/> + <stop stop-color="#FF0039" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd"> + <path d="M149.5 168.5c-.1 0-.1.1-.2.1l-3.3 1.5c-.2.1-.3.1-.5.2.7.3 1.4.5 2.2.5 1.6 0 3.1-.7 4.2-1.9 1-1.1 1.4-2.5 1.3-4-.1-.9-.3-1.7-.7-2.4l-1.6 4.4c-.3.6-.8 1.2-1.4 1.6zM178.7 206.1c-.1-.1-.2-.3-.2-.4l-2 2.7 3.1 1.1-.8-2.6c-.1-.2-.1-.5-.1-.8zM240.6 207.9h0zM168.5 200.6h-.2c-.2.2-.5.3-.7.4l-2.5.7.2.8c1.1.7 2 1.7 2.5 2.9l1 .4 3.7-5c.9-1.2 2.2-1.9 3.7-2l-.1-.3-2.5.7c-.2.1-.4.1-.6.1h-.2c-.2.2-.5.3-.7.4l-3.1.9c-.1-.1-.3 0-.5 0zM146.9 159.8c.1.1.2.1.3.2 0-.1.1-.2.1-.3-.1 0-.2 0-.4.1zM143. [...] + <path fill="#EDEDF0" fill-rule="nonzero" d="M227.5 226.4c.1.1.1.1.2.1 0 0-.1 0-.2-.1z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M228.2 231c-1.2 0-2.4-.4-3.4-1.2-1.3-1.1-2.1-2-2.4-7.2-3.4 0-6.7.1-9.9.2.6 3.3.2 4.4-.7 5.6-1 1.4-2.6 2.2-4.3 2.2-2.9 0-5.3-2.1-9.6-7-15.1 1.3-25.3 3.8-25.3 6.6 0 4.3 23.1 7.7 51.6 7.7s51.6-3.4 51.6-7.7c0-3.6-16.7-6.7-39.3-7.5-2.3 5.7-5.1 8.3-8.3 8.3z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M158.9 75.5h13.4c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-13.4c-.3 0-.6.2-.6.6.1.4.3.6.6.6zM155.4 85.7c0-.3-.2-.6-.6-.6h-13.4c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h13.4c.3-.1.6-.3.6-.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M134.3 114.7l.6-.4.4-.2c0-.7.1-1.3.2-2 0-.1.1-.2.1-.4-.4-.9-.8-2-1.2-3v6h-.1zM131.8 102.3c-.1-.3 0-.6.3-.7.3-.1.6 0 .7.3l.3.9V67h-13c.7 2.2 1.8 5.2 3.1 8.8.1.3 0 .6-.3.7h-.2c-.2 0-.4-.1-.5-.4-1.3-3.8-2.4-7-3.2-9.2h-3.4c1.1 3.8 3.1 10.1 5.8 18.2l-.1-.5c1.6 4.4 8.9 24.1 11.5 31l.4-.3v-9.5c-.6-1.4-1.1-2.7-1.4-3.5zM121.2 91.2c-3.9-10.9-6.6-19.6-7.9-24.2H7.1v98.7c0 .6 0 .9.1 1 .1 0 .4.1 1 .1h124c.6 0 .9 0 1-.1 0-.1.1-.4.1-1v-38.4l-1.6 1-.4.2c-.3.2- [...] + <path fill="#FFF" fill-rule="nonzero" d="M70.3 103.8c-5.6 0-10.2 4.6-10.2 10.2s4.6 10.2 10.2 10.2 10.2-4.6 10.2-10.2c.1-5.6-4.5-10.2-10.2-10.2zM137.7 124.4l-.9.6.9 2.1v-2.7zM135.3 121.7s0 .1 0 0l2.4-1.5v-.1l-2.4 1.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M134.8 126.3l-.5.3v39.1c0 1.9-.3 2.2-2.2 2.2H8.1c-1.9 0-2.2-.3-2.2-2.2V65.8h107c-.2-.8-.4-1.4-.4-1.8-.1-.6.3-1.2.9-1.3.6-.1 1.2.3 1.3.9.1.4.3 1.2.6 2.2h3.4l-.8-2.4c-.1-.3.1-.6.4-.7.3-.1.6.1.7.4 0 0 .3 1 .9 2.7h14.5v39.7c.6 1.5 1.3 3.1 1.8 4.4.4-.9.9-1.6 1.6-2.3V49.7c0-2.3-1.9-4.2-4.2-4.2H6.8c-2.3 0-4.2 1.9-4.2 4.2v118c0 2 1.8 3.7 3.9 3.7h127.3c1 0 1.9-.4 2.6-.9-.8-1.6-1.2-3.4-1.3-5.3 0-1.5.9-2.7 2.2-3.3-.8-.8-1.1-2-.7-3.1l1.1-2.9v-23.4c-1-2.1- [...] + <path fill="#FFF" fill-rule="nonzero" d="M129.1 125.6c-.1.1-.2.2-.4.2-.2.1-.4.1-.6.1.2 0 .4 0 .6-.1.2 0 .3-.1.4-.2l4.1-2.5v-.1l-4.1 2.6z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M137.7 120.2v.1l2.2-1.5M139 115.8c-.2-.5-.2-1-.3-1.5 0 .5.1 1 .3 1.5z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M133.8 171.4H6.5c-2.2 0-3.9-1.6-3.9-3.7v-118c0-2.3 1.9-4.2 4.2-4.2h126.6c2.3 0 4.2 1.9 4.2 4.2V107.6c.6-.7 1.4-1.3 2.2-1.8V81.2h27.6c.1-.2.2-.4.2-.6 0-.2.3-.2.3 0 .1.2.1.4.2.6h14.5c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-42.8V49.7c0-3.6-2.9-6.5-6.5-6.5H6.8c-3.6 0-6.5 2.9-6.5 6.5v118c0 3.3 2.8 5.9 6.1 5.9h127.3c1.4 0 2.7-.5 3.7-1.2-.4-.6-.8-1.3-1.1-1.9-.7.5-1.6.9-2.5.9z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M137.7 127.1l-.9-2.1-1.9 1.2c.9 2 1.9 4.1 2.9 6.3V156l1.2-3.2c.2-.5.6-1 1-1.3v-14.1c2.6 5.5 5.2 11 7.4 15.2h.4c.7 0 1.4.1 2.1.2-3.1-6.1-6.1-12.1-8.7-17.9-.2-.5-.7-1.5-1.2-2.7V123l-2.2 1.4v2.7h-.1z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M134.3 65.8h-14.5c-.6-1.7-.9-2.7-.9-2.7-.1-.3-.4-.4-.7-.4-.3.1-.4.4-.4.7l.8 2.4h-3.4c-.3-1-.5-1.8-.6-2.2-.1-.6-.7-1-1.3-.9-.6.1-1 .7-.9 1.3.1.4.2 1 .4 1.8H6v99.8c0 1.9.3 2.2 2.2 2.2h124c1.9 0 2.2-.3 2.2-2.2v-39.1l-1.1.7v38.4c0 .6 0 .9-.1 1-.1 0-.4.1-1 .1H8.2c-.6 0-.9 0-1-.1 0-.1-.1-.4-.1-1V67h106.2c1.3 4.6 4 13.3 7.9 24.2h.1c2.5 7.2 7.1 19.3 9.6 25.7l2-1.2c-2.7-6.9-9.9-26.7-11.5-31l.1.5c-2.8-8.1-4.7-14.4-5.8-18.2h3.4c.8 2.2 1.8 5.4 3.2 9.2. [...] + <path fill="#D7D7DB" fill-rule="nonzero" d="M95.6 109.8h-7.1c-.4-2.1-1.2-4-2.3-5.8l5.1-5.1c.7-.9 1-2 .8-3.1-.2-1.1-.7-2.1-1.6-2.8-.7-.6-1.6-.8-2.5-.8-.9 0-1.8.3-2.6.9l-5.1 5.1c-1.8-1.1-3.7-1.8-5.8-2.3v-7.1c0-2.3-1.9-4.2-4.2-4.2-2.3 0-4.2 1.9-4.2 4.2v7.1c-2.1.4-4 1.2-5.8 2.3l-4.7-5.1c-.8-.8-2-1.3-3.1-1.3-1.2 0-2.3.5-3.1 1.3-.8.8-1.3 2-1.3 3.1 0 1.2.5 2.3 1.3 3.2l5.1 4.7c-1.1 1.8-1.8 3.7-2.3 5.8H45c-2.3 0-4.2 1.9-4.2 4.2 0 2.3 1.9 4.2 4.2 4.2h7.1c.4 2.1 1.2 4 2.3 5.8l-5 4.7c-1.9 1.4-2. [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M33.7 25.5h97.9c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-22.8c-2-3.7-7.1-11.7-13.4-12.9-8.4-1.6-10 6.7-10 6.7S79.8 2.6 65.8 4.5c-6.5.9-9 4.2-9.8 7.8h.1c.3 0 .6.2.6.5 0 .4.1.7.1 1.1 0 .3-.2.6-.5.6h-.1c-.2 0-.4-.1-.5-.3-.1 1.9.1 3.8.5 5.3H57c-.1-.3-.2-.6-.4-1-.1-.3.1-.6.4-.7.3-.1.6.1.7.4.3 1 .6 1.7.6 1.7.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-1.3c.4 1.5.9 2.5.9 2.7H33.7c-.6 0-1.1.5-1.1 1.1 0 .5.5 1 1.1 1z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M205.5 42.3c.1 0 .3-.1.4-.2.6-.7 1.5-1.1 2.6-1.4.3-.1.5-.4.4-.7-.1-.3-.4-.5-.7-.4-1.3.4-2.4.9-3.1 1.7-.2.2-.2.6 0 .8.1.2.3.2.4.2zM212.7 40.5c.4.1.7.2 1 .3h.2c.2 0 .5-.1.5-.4.1-.3-.1-.6-.4-.7-.4-.1-.8-.2-1.1-.3-.3-.1-.6.1-.7.4 0 .4.2.7.5.7zM238.3 50.7h3.3c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-3.3c-.3 0-.6.2-.6.6 0 .4.3.6.6.6zM221.2 46.7c.3-1 1.2-3.2 3.8-3.2.3 0 .7 0 1 .1 1.6.3 3.2 1.3 4.8 3 .2.2.6.2.8 0 .2-.2.2-.6 0-.8-1.8-1.9-3.6-3-5.4-3.4-.4-. [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M191.7 54.6h54.4c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-12.9c-1.1-2.1-3.9-6.5-7.4-7.2-2.4-.5-3.8.5-4.6 1.6 0 .1-.1.2-.2.3-.6 1-.8 1.9-.8 1.9s-3.1-8-10.9-7c-5.7.8-6 5-5.4 7.8h.7s.1-.1.2-.1c.3-.1.6 0 .7.3l.1.1c.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-.9c.2.9.5 1.4.5 1.5h-13.2.2c-.6 0-1.1.5-1.1 1.1-.1.6.4 1.1 1.1 1.1z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M107.4 231.1c-4 0-5.8-2.5-6.2-4.6l-.1-.5c-7 .5-12.1 2.1-12.1 4 0 2.3 7.3 4.1 16.3 4.1s16.3-1.8 16.3-4.1c0-1.4-2.7-2.6-6.7-3.3-.2.7-.6 1.3-1 1.9-2 2.4-5.7 2.5-6.5 2.5z"/> + <path fill="#FFF" fill-rule="nonzero" d="M227.3 225.7c-.1-.3-.1-.6-.1-1 0 .4 0 .7.1 1zM228 226.5h-.1.1zM226.9 216v0zM199.5 218.8c.3.3.5.6.8.9-.3-.3-.6-.6-.8-.9z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M237.7 208.7c1-.3 1.9-.5 2.8-.8h.1c6.5-2 12.4-4.7 17.4-7.6-7.2 3.5-15 5-20 5.6 0 1-.1 1.9-.3 2.8z"/> + <path fill="#FFF" fill-rule="nonzero" d="M241.9 163c.1-.2.2-.4.2-.6 0 .2-.1.4-.2.6zM234.1 70.2c-.3 0-.5-.1-.8-.1-.3 0-.7 0-1 .1.3 0 .7-.1 1-.1.2 0 .5 0 .8.1zM232 70.2c-2.5.4-4.6 2.2-5.5 4.5.9-2.3 3-4 5.5-4.5zM219.1 84.9c0-.1 0-.1 0 0 0-.1 0-.1 0 0zM221.2 79.8c.5-.5 1.1-.9 1.7-1.3-.6.3-1.2.8-1.7 1.3zM226 76.7c-.4.3-.7.7-1 1-.7.1-1.4.4-2.1.7.6-.3 1.3-.6 2.1-.7.3-.3.7-.6 1-1zM207.3 226.3s0-.1 0 0h-.1c0-.1.1 0 .1 0zM263.6 172.3c-.4.1-.8.3-1.2.4.4-.1.8-.2 1.2-.4zM248.7 65.6h.8c-.3.1-.5 0- [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M235.8 218c-.5 1.4-1.1 3.2-2 4.9-1.5 3.1-3.5 5.9-5.8 5.9-.7 0-1.3-.2-1.8-.6-.6-.5-1.2-.9-1.4-5.4-.1-1.5-.1-3.5-.1-6.2-.5 0-1-.1-1.6-.2l-.7-.2c0 2.8 0 4.9.1 6.6.2 5.1 1 6.1 2.4 7.2 1 .8 2.1 1.2 3.4 1.2 3.2 0 6-2.7 8.4-8.1.6-1.3 1.2-2.8 1.7-4.4.7-2 1.3-4.8 1.9-8.2-.9.3-1.9.5-2.9.8-.5 2.6-1.1 4.9-1.6 6.7zM265 198.7c-2.4 1.6-5 3.1-7.8 4.7 1.6-.7 3.1-1.4 4.6-2.3h.2c3.7 0 7.1-.5 10.2-1.4-1.7-.2-3.3-.6-4.7-1.3-.8.1-1.6.2-2.5.3z"/> + <path fill="#FFF" fill-rule="nonzero" d="M284.9 173c.8-.7 1.8-1.2 2.9-1.2.4 0 .7 0 1 .1h.1c-.7-.6-1.5-1.1-2.4-1.2-.3-.1-.6-.1-.8-.1-.8 0-1.6.2-2.3.6l-.2.2c.6.6 1.2 1.1 1.7 1.6zM287.7 188c.3-.6.6-1.1.9-1.7-.4.3-.8.6-1.3.8.1.4.3.6.4.9z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M266.3 154.9c-1.2.6-2.1.9-2.7 1.2-.3.1-.6.2-.8.3-.2.1-.3.1-.3.1-.2.1-.4.1-.7.1-.6 0-1.2-.3-1.7-.7-6.4 3.3-13.7 6.8-16.1 7.9-.1.2-.2.4-.2.6.8-.1 1.7-.1 2.5-.1 3.5 0 6.8.6 9.7 1.8 2.7 1.1 5 2.5 7 4.3 6.4-2.4 7.9-7.8 7.9-8 .2-.9.9-1.5 1.8-1.7h.3c.8 0 1.5.4 1.9 1.1.1.2 1.7 2.9 2.3 7.1 1 .2 2 .6 2.9 1-.5-5.5-2.5-9.1-2.9-9.7-.9-1.4-2.4-2.3-4-2.3-.2 0-.5 0-.7.1-1.9.3-3.4 1.7-3.9 3.5 0 .1-1 3.6-5.1 5.7-1.9-1.4-4-2.7-6.4-3.7-1.3-.6-2.8-1-4.2-1.3 5.1 [...] + <path fill="#FFF" fill-rule="nonzero" d="M265.3 137.7h.5-.5zM246.3 166.4h-.3H246.3z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M284.3 126.3l1.2-1.8c4.6-2.2 7.4-7.2 6.8-12.3v-.2c1.4-2.3 2-5 1.7-7.7-.3-2.4-1.3-4.6-2.8-6.4-.3-2.1-.7-4.1-1.3-6.1v-1c0-4-2-7.7-5.3-9.9-2.8-4.1-6.5-7.8-10.6-10.6-1.8-4.4-6.2-7.3-11-7.3-1.4 0-2.9.3-4.3.8-.8-.2-1.6-.4-2.4-.5-2.1-1.6-4.7-2.5-7.3-2.5-.5 0-1 0-1.5.1-2.2.3-4.4 1.2-6.1 2.6-2.1.4-4.2 1.1-6.2 1.9-.6-.1-1.1-.1-1.7-.1-5.2 0-9.9 3.5-11.4 8.4-4.4 1.8-7.4 6.2-7.4 11.1v.2c-.2.5-.4 1-.6 1.4-.4.4-.8.7-1.2 1.2-.1-2.2-1.1-4.2-2.2-6.3-.2-.4-.4 [...] + <path fill="#FFF" fill-rule="nonzero" d="M287.7 101.9c-.4-.6-.9-1.1-1.4-1.5.6.4 1 .9 1.4 1.5zM286.9 111.2c.2.6.4 1.2.5 1.9 0 .2 0 .5.1.7 0-.2 0-.5-.1-.7-.1-.7-.3-1.3-.5-1.9zM289 106c0-.3 0-.6-.1-.9 0-.4-.1-.7-.2-1.1.1.3.2.7.2 1.1.1.3.1.6.1.9zM263.6 137.5c-.3-.1-.7-.1-1-.2h-.1.1c.3.1.6.1 1 .2zM259.6 140.2c.4-.1.7-.2 1.1-.4-.4.2-.7.4-1.1.4zM188.5 192.2v0zM218.2 88.3c-.2.4-.4.9-.5 1.3-.2.1-.3.1-.5.2.1-.1.3-.2.5-.2.1-.4.3-.9.5-1.3zM186 170.6c0-.1-.1-.1-.1-.2 0 .1 0 .2.1.2zM215.8 97.7c0-. [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M207.5 230.7c1.7 0 3.3-.8 4.3-2.2.9-1.2 1.3-2.3.7-5.6-.3-2-1.1-4.8-2.2-8.8 1 0 2 .1 3 .1h2.1l-3.8-1.1-4.8-.6c1.6 5.2 2.4 8.5 2.9 10.6.6 3.2.3 3.7-.2 4.3-.5.8-1.4 1.2-2.3 1.2-1.7 0-3.4-1.3-6.6-4.9-.8-.9-1.8-2-2.9-3.3-3.3-3.8-5.6-8.6-7.1-12.7-1-.5-2-1.1-2.9-1.7 1.6 4.8 4.3 11 8.4 15.8.6.8 1.3 1.5 1.8 2.1 4.4 4.8 6.7 6.8 9.6 6.8zM185.9 201.9c1.2.9 2.4 1.7 3.6 2.5l-.3-.9c-2.1-3-3.1-7-3-11.4-.4-.3-.8-.5-1.2-.8-.2-.2-.3-.5-.1-.8.2-.2.5-.3.8-.1.2. [...] + <path fill="#FFF" fill-rule="nonzero" d="M206.8 158.8c.5-.4 1-.9 1.6-1.3-.6.4-1.1.9-1.6 1.3z"/> + <path fill="url(#a)" fill-rule="nonzero" d="M237.4 200.4c-.1 1-.2 2-.2 2.9 4.1-.4 10.5-1.6 17.1-4.5 1.7-.8 3.3-1.6 4.7-2.6-.7-.2-1.4-.6-1.9-1.1-3.5 1.9-8.3 4-14.2 4.8-1.9.2-3.7.4-5.5.5z"/> + <path fill="url(#b)" fill-rule="nonzero" d="M268.8 169.3c1.6-.6 3.4-.9 5.1-.9.4 0 .7 0 1.1.1-.4-2.3-1.1-4-1.5-4.9-.1-.3-.3-.5-.3-.6v-.1s0 .1-.1.3c0 .1-.1.2-.1.3-.1.1-.1.3-.2.5-.7 1.2-1.8 3.3-4 5.3z"/> + <path fill="url(#c)" fill-rule="nonzero" d="M274.9 176.9c-.3 0-.6-.1-.9-.1-2.6 0-5 1.4-6.3 3.6-.3.5-.7 1-1.1 1.4l1.6.4c0-.1.1-.2.2-.3l.9-.7c.2-.2.6-.1.8.1.2.2.1.6-.1.8l-.5.4.9.2c.8.2 1.5.6 2 1.2.7-1.4 1.3-2.8 1.8-4.1.2-1 .5-1.9.7-2.9z"/> + <path fill="url(#d)" fill-rule="nonzero" d="M189.9 156.3c-2.8-1.8-6-2.6-9.1-2.6-5.6 0-11.1 2.8-14.3 7.8-5 7.9-2.7 18.3 5.2 23.3 2.8 1.8 6 2.6 9.1 2.6 2.1 0 4.2-.4 6.1-1.1.3-1.5.7-3.1 1.2-4.6-.5 0-1-.1-1.5-.4-1.5-.8-2.1-2.6-1.3-4s2.6-1.9 4.1-1.1c.3.2.5.3.7.5.6-1.3 1.3-2.6 2-3.9-3.2.4-4.2.5-4.7.5h-.6c-1-.1-2.3-.6-2.9-1.8-.5-.8-.7-2.3.5-4.3.5-.7 1.1-1.9 10.6-5.9-1.4-1.9-3-3.7-5.1-5zm-14.1 2.1c1.7 0 3.1 1.3 3.1 2.9 0 1.6-1.4 2.9-3.1 2.9-1.7 0-3.1-1.3-3.1-2.9 0-1.6 1.4-2.9 3.1-2.9zm-8.7 1 [...] + <path fill="url(#e)" fill-rule="nonzero" d="M204.7 160.7c-9.4 3.5-17.8 8.2-17.9 8.3-.1.1-.2.1-.3.1-.2 0-.4-.1-.5-.3-.2.3-.3.6-.3.9v.6c0 .1 0 .2.1.2 0 .1.1.1.1.2.4.5 1.2.5 1.2.5H188c.2 0 .4 0 .6-.1.3 0 .6-.1.9-.1h.2c.3 0 .6-.1.9-.1h.2c.4 0 .7-.1 1.1-.2h.1c.9-.1 2-.3 3.1-.5 2.9-4 5-6.2 5.1-6.4.2-.2.6-.2.8 0 .1.1.2.2.2.4 1.1-1.2 2.2-2.3 3.5-3.5z"/> + <path fill="url(#f)" fill-rule="nonzero" d="M187.9 167.1c-.1 0-.1.1-.2.1s-.1.1-.2.1c1.1-.6 2.7-1.4 4.8-2.5-1.8.9-3.4 1.7-4.4 2.3z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M213.3 204.7c-.7-.2-1.3-.7-1.7-1.2l-.4 1.3 1.9.5c.3-.1.7-.2 1-.3l-.8-.3z"/> + <path fill="#FFF" fill-rule="nonzero" d="M188.7 193.1c0 .1-.1.1-.1.1v1.6c0 .4.1.8.2 1.2 0 .2.1.4.1.5l.3 1.2c0 .2.1.3.1.5.1.4.3.8.4 1.2v.1c.8 1.6 2.9 4.5 8.2 5.4.3.1.5.3.4.6-.1.3-.3.5-.5.5h-.1c-2.7-.5-4.6-1.5-6-2.5l.3.9c.2.5.3 1 .5 1.5 1.4.7 2.7 1.2 4.1 1.7 2.4.8 4.8 1.4 7.2 1.9 0-.1-.1-.3-.1-.4 0-.1-.1-.3-.1-.4-.2-.5-.4-1-.5-1.5-.1-.3-.2-.6-.3-.8h.2c0-.6 0-1.1.2-1.7l1.2-4.1c-.7-.2-1.5-.4-2.2-.6-.3-.1-.5-.4-.4-.7.1-.3.4-.5.7-.4.7.2 1.5.4 2.2.6l4.1-14.3c.7-2.4 2.9-4 5.4-4 .5 0 1 .1 1.6 [...] + <path fill="url(#g)" fill-rule="nonzero" d="M203.6 209.1c0-.1 0-.1 0 0-.1-.2-.2-.3-.2-.4.1.1.1.2.2.4z"/> + <path fill="url(#h)" fill-rule="nonzero" d="M222.3 203.8l-1.9-.6c0 .1 0 .2-.1.3-.4 1.3-1.6 2.2-2.9 2.2-.3 0-.6 0-.9-.1l-2.4-.7c-.3.1-.7.2-1 .3l10 2.9 1.3-4.5c-.4.2-.8.3-1.3.3-.2.1-.5 0-.8-.1z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M227.1 224.7c0 .4.1.7.1 1 0 .3.1.5.2.6 0 .1.1.1.1.1.1.1.1.1.2.1H228.3s.1 0 .1-.1c.1 0 .1-.1.2-.1l.1-.1c.1 0 .1-.1.2-.1l.1-.1.2-.2.1-.1c.1-.1.2-.2.2-.3l.1-.1c.1-.2.3-.3.4-.5v-.1c.1-.2.2-.3.4-.5 0-.1.1-.2.1-.2.1-.1.2-.3.3-.4.1-.1.1-.2.2-.3.1-.1.1-.2.2-.3-1.4 0-2.9-.1-4.3-.1v.9c.2.2.2.5.2.9z"/> + <path fill="url(#i)" fill-rule="nonzero" d="M230 212.6c-.5 1.6-1.6 2.8-3.1 3.5v7.5c0 .4.1.7.1 1.1 0 .4.1.7.1 1 0 .3.1.5.2.6 0 .1.1.1.1.1.1.1.1.1.2.1H228.2s.1 0 .1-.1c.1 0 .1-.1.2-.1l.1-.1c.1 0 .1-.1.2-.1l.1-.1.2-.2.1-.1c.1-.1.2-.2.2-.3l.1-.1c.1-.2.3-.3.4-.5v-.1c.1-.2.2-.3.4-.5 0-.1.1-.2.1-.2.1-.1.2-.3.3-.4.1-.1.1-.2.2-.3.1-.1.1-.2.2-.3 0 0 0-.1.1-.1.1-.1.1-.2.2-.3.1-.2.2-.3.2-.5.1-.1.1-.2.2-.4s.2-.4.2-.5c.1-.1.1-.3.2-.4.1-.2.2-.4.2-.6.1-.1.1-.3.2-.4.1-.2.2-.5.3-.7 0-.1.1-.2.1-.4.1-.4 [...] + <path fill="url(#j)" fill-rule="nonzero" d="M240.1 167.1c-.1.2-.3.3-.5.3h-.2c-.3-.1-.4-.4-.3-.7.4-.9.9-2.1 1.4-3.2-5.6 9-7.5 16.2-9.5 22.5l.9.3c1.4.4 2.6 1.4 3.3 2.7.7 1.3.9 2.8.5 4.3l-4.9 17.1c1.6-.3 3.2-.6 4.7-.9v-.1-.1c.1-.6.2-1.1.2-1.7v-.1c0-.2.1-.5.1-.7 0-.1-.1-.2 0-.3.5-4.5 1.9-23.7 1.9-23.9 0-.3.3-.5.6-.5s.5.3.5.6c0 .1-.7 9.5-1.3 16.7 1.7-.1 3.5-.2 5.3-.5 5.6-.8 10.3-2.8 13.7-4.7-.5-.9-.6-2-.4-3l1.9-7.3c.4-1.4 1.4-2.4 2.6-2.8-.9-1.2-1-2.9-.3-4.3.8-1.6 2-3.1 3.3-4.3-.4.1-.8.3-1 [...] + <path fill="url(#k)" fill-rule="nonzero" d="M202.7 206.4c.1.2.2.5.3.8 0-.3-.1-.5-.1-.8h-.2z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M194.4 210.8c.3.6.6 1.3 1 1.9 0 .1.1.1.1.2.3.6.7 1.3 1.1 1.9 0 .1.1.1.1.2l1.2 1.8.1.1c.5.6.9 1.3 1.5 1.9.3.3.5.6.8.9.1.1.1.2.2.2l.6.6c.1.1.2.2.2.3.2.2.3.4.5.5.1.1.2.2.2.3.2.2.3.3.4.5.1.1.2.2.2.3l.5.5.2.2.2.2.4.4.1.1.5.5.2.2.3.3.2.2.3.3c.1.1.1.1.2.1.1.1.2.1.3.2l.1.1c.1.1.2.1.3.2 0 0 .1 0 .1.1.1.1.2.1.3.2h.1c.1 0 .2.1.2.1h.6c.1 0 .2-.1.2-.2 0 0 .1-.1.1-.2v-.1-.3-.1c0-.2 0-.4-.1-.6v-.2c0-.2-.1-.4-.1-.6v-.2c0-.2-.1-.4-.1-.6v-.2-.1c-.1-.3-.1-.6- [...] + <path fill="url(#l)" fill-rule="nonzero" d="M241.8 136.3c-7.4 7.4-10.4 9.3-19.4 11-14.7 1.3-34.1-.7-39.2-3.2-5.6-2.7-12-17.9-12-18.1-.1-.5-.5-.8-1-.8-.4 2.5.9 6.1 2.9 9.7.3.6.7 1.2 1.1 1.8.6.9 1.2 1.8 1.8 2.6.4.6.8 1.1 1.2 1.6.4.5.8 1 1.3 1.5.2.2.4.5.6.7.4.4.8.8 1.3 1.2.2.2.4.3.6.5.8.6 1.6 1.1 2.3 1.4 6.8 2.5 16.4 4.1 26.3 4.1 1.7 0 3.4-.1 5.1-.2h.2c.1 0 12.1-1.3 17.8-3.6.3-.1.6 0 .7.3.1.3 0 .6-.3.7-3.8 1.5-10.1 2.6-14.2 3.2-.3.2-.6.3-1 .5 10.2 0 19.5-1.3 23.1-4.7 4.3-4 3.1-12.5.8-10.2z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M250.4 158.2c-3.6 1.7-6.5 3.1-7.6 3.6 2.1-1 4.8-2.2 7.6-3.6z"/> + <path fill="url(#m)" fill-rule="nonzero" d="M224.6 99.7c.7 0 1.4-.1 2.1-.1v-.2c0-.3.3-.6.6-.5l3.3.1c.3-1.1.7-1.7.8-1.7.2-.2.5-.3.8-.1.2.2.3.5.1.8-.1.1-1.4 1.9-.1 4.9.6 1.5 2.9 2.8 3.8 3.2.2.1.3.3.3.5 0 0 .4 4.6 3.5 8.4 3.4 4.2 8.4 5.7 8.5 5.7.2.1.4.2.4.4 0 0 .6 3.3 1.9 4.6.7.7 2.6 2.6 7.4 1.7.3-.1.6.1.6.4.1.3-.1.6-.4.6-1 .2-1.9.3-2.7.3-3.1 0-4.7-1.2-5.8-2.2-1.3-1.3-1.9-3.9-2.1-4.8-1.1-.4-4.5-1.7-7.4-4.6.6 1 1.2 1.9 2 2.9v.1c0 3.5 2.3 6.4 5.4 7.4.7 1.5 1.3 3.2 1.6 5.3h.1c.3-.1.6.1.7.4 [...] + <path fill="url(#n)" fill-rule="nonzero" d="M233 105.8c.5.6 1.1 1.2 1.8 1.6.1.3.3.7.5 1.1-.1-.6-.2-1.1-.3-1.4-.4-.3-1.2-.7-2-1.3z"/> + <path fill="url(#o)" fill-rule="nonzero" d="M202.5 90.4c1.3 1.2 3.4.6 4.4-.9v-.1c0-.1.3-5.4-2.2-7.4 0 0-.1 0-.1.1l-.2.2s-.1.1-.1.2c-.1.1-.1.2-.2.3 0 .1-.1.1-.1.2-.1.1-.1.2-.2.4 0 .1-.1.1-.1.2-.1.2-.2.3-.3.5 0 .1-.1.1-.1.2-.1.2-.2.5-.3.7 0 .1 0 .1-.1.2-.1.2-.2.4-.2.6 0 .1-.1.2-.1.3-.1.2-.1.3-.2.5 0 .1-.1.2-.1.3 0 .2-.1.3-.1.5 0 .1 0 .2-.1.3 0 .1-.1.3-.1.4V89.7c0 .1 0 .2.1.3v.2c0 .1.1.3.2.3.1-.2.1-.2.2-.1z"/> + <path fill="url(#p)" fill-rule="nonzero" d="M209.1 94.2V94c-.1-.5-.3-1.4-.7-2.6-.1-.3-.2-.5-.3-.7-.6 2.1-3.7 3.3-5.8 1.5 0 .4-.1.7-.1 1.1 0 .6 0 1.1.1 1.6s.2 1 .4 1.3c.1.1.1.2.2.2.1.1.3.2.4.3.1.1.3.1.4.2 2.1.7 4.6-.6 5.4-2.7z"/> + <path fill="url(#q)" fill-rule="nonzero" d="M204 98c0 .3.1.5.1.8-.1-.7-.2-1.3-.3-2 .1.4.1.8.2 1.2z"/> + <path fill="url(#r)" fill-rule="nonzero" d="M210.6 95.5c-.4 2.7-3.6 4.7-6.5 3.5v.2c.1.4.2.8.2 1.1.4 1.6 1 2.9 1.6 3.4 2.8.5 6.9-1.5 7.1-4.5v-.5c-.2-.6-.5-1.4-1.1-2.1-.4-.4-.9-.8-1.3-1.1z"/> + <path fill="url(#s)" fill-rule="nonzero" d="M214.2 112c-.1 0-.1-.1 0 0-.2-.4 0-.7.3-.8.2-.1 3.9-1.4 3.9-5.2 0-2.6-2.1-4.5-3.5-5.5.2 3.4-3.6 6.1-7 6 1.5 2.5 3.4 3 3.5 3 .6.1 1 .7.9 1.3-.1.5-.6.9-1.1.9.2.2.5.3.7.3.6.3 1.5.2 2.3 0z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M240.4 163.4c-.5 1.1-1 2.3-1.4 3.2-.1.3 0 .6.3.7h.2c.2 0 .4-.1.5-.3.7-1.7 1.6-3.7 2.1-4.6.1-.1.1-.3.2-.4.2-.1.3-.2.5-.2 1-.5 4-1.9 7.6-3.6 3-1.4 6.1-3 9-4.5 0-.3.1-.6.3-.9 0 0 0-.1.1-.2-5.5 2.5-17.4 8.1-18 8.5-.3.2-.8 1.2-1.4 2.3z"/> + <path fill="url(#t)" fill-rule="nonzero" d="M275.8 140c-.7.2-1.3.6-2 .9v.4c.4 1.9 1.8 3.4 3.5 4.2.5-.6.9-1.1 1.3-1.7.3-.5.6-.9.8-1.3-.8-.2-1.5-.6-2-1.1-.4-.5-.7-1-.9-1.5-.2-.1-.4 0-.7.1z"/> + <path fill="url(#u)" fill-rule="nonzero" d="M273.3 149.1c.1 0 .1-.1.2-.1l.6-.5c.2-.1.4-.3.6-.4.1-.1.2-.2.3-.2-.3-.2-.7-.4-1-.7-1.4-1.1-2.5-2.7-3-4.4-.1.1-.2.1-.3.2-.2.1-.4.3-.6.4l-.6.5c-.2.2-.4.3-.6.5-.6.5-1.2 1-1.7 1.5-.6.5-1.1 1-1.6 1.5-.9.9-1.8 1.9-2.6 2.9s-1.4 1.8-1.7 2.2c-.2.3-.3.5-.4.7l-.1.2c-.2.3-.2.7-.1 1 .1.4.3.7.6.8.3.2.7.2 1 .1 0 0 .1 0 .3-.1.2-.1.4-.1.7-.3.6-.2 1.4-.6 2.6-1.1h.1c-1.2-2.3-.6-5.1-.6-5.3.1-.6.7-1 1.3-.8.4.1.7.4.8.8 1.8-.4 4.1 0 5.8.6z"/> + <path fill="url(#v)" fill-rule="nonzero" d="M281.4 141.3c.9-.2 2.7-.9 4.2-3.5-1-.8-2.6.8-3.3-.6-.6-1.1.1-1.5-.4-2.1h-.6c-1.6 0-2.9.7-3.5 1.9-.6 1.1-.3 2.5.6 3.5.6.8 1.8 1.1 3 .8z"/> + <path fill="#FFF" fill-rule="nonzero" d="M267.5 148.5c.1.2.1.4 0 .6 0 0-.5 2.5.5 4.1.5.8 1.3 1.2 2.4 1.4 1.3.2 2.3 0 3.1-.6 1.2-.9 1.4-2.7 1.4-2.7 0-.4.3-.7.6-.9-.3-.3-.7-.5-1.1-.8-.3-.2-.7-.4-1.2-.5-1.6-.6-3.9-1-5.7-.6z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M148 139.7c-.3.1-.4.5-.3.7 2 4.3 4 8.2 6 12 .1.2.3.3.5.3.1 0 .2 0 .3-.1.3-.1.4-.5.2-.8-2-3.7-4-7.6-6-11.9-.1-.2-.4-.4-.7-.2zM160.4 163.3c.1 0 .2 0 .3-.1.3-.2.3-.5.2-.8-.6-.9-1.2-1.9-1.8-2.8-.1-.1-.2-.2-.4-.3.6 1.2 1 2.4 1.2 3.7.2.2.4.3.5.3zM143.3 129.9h-.2v.4l.4 1c.1.2.3.3.5.3h.2c.3-.1.4-.5.3-.7l-.4-1h-.8zM283.4 171.4c1.5-1.3 3.1-2.7 4.6-4.1.2-.2.2-.6 0-.8-.2-.2-.6-.2-.8 0-1.7 1.5-3.3 3-5 4.4.3.2.6.4.9.7.2-.1.2-.2.3-.2zM185 190.5c-.2.2-.1.6 [...] + <path fill="#FFFEFE" fill-rule="nonzero" d="M235.2 188.8c-.7-1.3-1.9-2.3-3.3-2.7l-.9-.3-15.3-4.4c-.5-.1-1-.2-1.6-.2-2.5 0-4.7 1.7-5.4 4l-4.1 14.3-.3 1.1-1.2 4.1c-.2.6-.2 1.1-.2 1.7 0 .3 0 .5.1.8.1.5.2 1 .5 1.5.1.1.1.2.1.3 0 0 0 .1.1.1.1.2.2.4.4.5.7 1 1.7 1.7 2.9 2.1l4.8 1.4 3.8 1.1 7 2 .7.2c.5.1 1 .2 1.6.2.8 0 1.5-.2 2.2-.5 1.5-.7 2.6-1.9 3.1-3.5l.7-2.3 4.9-17.1c.3-1.5.1-3.1-.6-4.4zm-1.7 3.7l-5.6 19.4c-.4 1.5-1.8 2.4-3.2 2.4-.3 0-.6 0-.9-.1l-16.2-4.7c-1.8-.5-2.8-2.4-2.3-4.2l5.6-19.4c [...] + <path fill="#FFFEFE" fill-rule="nonzero" d="M228.7 191.1l-12.9-3.7c-.2 0-.3-.1-.5-.1-.7 0-1.4.5-1.6 1.2l-4.7 16.2c-.3.9.3 1.8 1.2 2.1l12.9 3.7c.2 0 .3.1.5.1.7 0 1.4-.5 1.6-1.2l4.7-16.2c.2-.9-.4-1.9-1.2-2.1zm-14.4 7.2c.1-.4.4-.6.8-.6h.2l8.1 2.3c.4.1.7.6.6 1-.1.4-.4.6-.8.6h-.2l-8.1-2.3c-.5-.1-.7-.5-.6-1zm-.9 3.2c.1-.4.4-.6.8-.6h.2l3.2.9c.4.1.7.6.6 1-.1.4-.4.6-.8.6h-.2l-3.2-.9c-.5 0-.8-.5-.6-1zm9.7 6.7l-10-2.9-1.9-.5.4-1.3c.4.6 1 1 1.7 1.2l.9.2 2.4.7c.3.1.6.1.9.1 1.4 0 2.6-.9 2.9-2.2 0- [...] + <path fill="#FFFEFE" fill-rule="nonzero" d="M166.9 216.4c-.3-.9-1.1-1.5-2-1.5-.2 0-.4 0-.6.1-1.1.3-1.7 1.5-1.4 2.6.3.9 1.1 1.5 2 1.5.2 0 .4 0 .6-.1h.2c1-.3 1.6-1.4 1.3-2.4-.1-.1-.1-.2-.1-.2zM163.9 207.1c-.3-.8-1.1-1.3-1.9-1.3-.3 0-.5 0-.8.1-1.1.4-1.6 1.6-1.2 2.7.3.8 1.1 1.3 1.9 1.3.3 0 .5 0 .8-.1.1 0 .1 0 .2-.1 1-.4 1.5-1.5 1.1-2.5l-.1-.1zM136.1 109.9c-.3.6-.5 1.2-.6 1.8 0 .1-.1.2-.1.4-.1.7-.2 1.3-.2 2l-.4.2-.6.4-.9.6-.2.1-.4.3-2 1.2-2.7 1.7-2.3 1.4c-.3.1-.5.3-.7.5-1.8 1.4-2.5 3.9-1. [...] + <path fill="#D7D7DB" fill-rule="nonzero" d="M154.8 144.4l-2.2-4.7c-.1-.3-.5-.4-.7-.3-.3.1-.4.5-.3.7l2.2 4.7c.1.2.3.3.5.3.1 0 .2 0 .2-.1.3 0 .4-.4.3-.6zM161 185.3c.1.2.3.2.5.2.1 0 .2 0 .3-.1.3-.2.3-.5.1-.8l-2.5-3.5c-.2-.3-.5-.3-.8-.1-.3.2-.3.5-.1.8l2.5 3.5z"/> + <path fill="#E1E1E6" fill-rule="nonzero" d="M179 214.8l-3.8-1.3c-.2-.1-.3 0-.5.1-.1.1-.2.2-.2.3-.1.3.1.6.4.7l3.8 1.3h.2c.2 0 .5-.1.5-.4v-.4c-.1-.2-.2-.3-.4-.3zM179.7 181.2c.1 0 .3 0 .4-.1.2-.2.3-.5.1-.8l-2.7-3.2-1.6-1.9c-.2-.2-.5-.3-.8-.1-.2.2-.3.5-.1.8l.9 1.1 3.3 4c.2.1.4.2.5.2z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M268.1 159.5l-6 5.5c-.2.2-.2.6 0 .8.1.1.3.2.4.2.1 0 .3 0 .4-.1l6-5.5c.2-.2.2-.6 0-.8-.2-.3-.5-.3-.8-.1zM125.9 112.2c.1.2.3.4.5.4h.2c.3-.1.4-.4.3-.7l-1.7-4.7c-.1-.3-.4-.4-.7-.3-.3.1-.4.4-.3.7l1.7 4.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M98.9 188.6c.6-.9 1.6-1.5 2.5-1.7-1.1.2-2.2.8-2.9 1.8-.2.2-.3.5-.4.7h.2c.2-.2.3-.5.6-.8zM113 187.5c-.7-.2-1.3-.4-2-.3-1.6 0-3.3.8-4.3 2.1 0 .1.1.2.1.3 1.4-2 3.9-2.8 6.2-2.1zM95.5 213.7c.3.6.7 1.2 1.3 1.6.5.4 1.1.6 1.7.6l-.1-.1c-.6-.1-1.2-.3-1.7-.7-.6-.4-1-.9-1.2-1.4zM90.5 210.7c-2-1.5-2.9-4-2.4-6.3-.6 2.4.3 5 2.4 6.5 1.1.8 2.4 1.1 3.6 1.1.3 0 .7 0 1-.1v-.2c-1.6.4-3.2.1-4.6-1zM91.6 191.8c.4-.5.8-1 1.3-1.4-.6.4-1.2.9-1.6 1.6-1.8 2.5-1.4 5.9.8 7. [...] + <path fill="#FFF" fill-rule="nonzero" d="M114.5 211.4c.3.1.5.4.5.7-.4 1.9-1 4.2-2.5 5.8l-.1.1c-.5.6-1.2 1.1-2 1.4.9-.4 1.6-.9 2.1-1.5l.1-.1c1.4-1.6 2-3.9 2.4-5.8.1-.3-.1-.6-.5-.7h-.1c-.3 0-.5.2-.6.5v.1c.1-.3.4-.5.7-.5z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M121.5 195.5c.4-1.4.5-2.9.2-4.4-.4-2.7-1.9-5.1-4.2-6.8-1.8-1.3-3.9-2-6.1-2-1.4 0-2.7.3-4 .8-1.4-.9-3.1-1.3-4.8-1.3-2.4 0-4.7.9-6.4 2.5-3.3.1-6.5 1.8-8.4 4.5-1.9 2.7-2.5 6.2-1.6 9.3-.3.3-.6.7-.9 1.1-3.5 4.9-2.4 11.7 2.5 15.2 1.2.8 2.5 1.4 3.8 1.8.6 1 1.4 1.9 2.4 2.6 1.2.9 2.6 1.4 4 1.5.7.6 1.5 1 2.4 1.5l.7 4.2.1.5c.3 2.1 2.2 4.6 6.2 4.6.7 0 4.4-.1 6.5-2.6.5-.6.8-1.2 1-1.9.2-.8.3-1.6.2-2.4l-.3-2.1c.4-.3.8-.7 1.2-1.1l.2-.2c2.2-2.5 3.1-5.7 3.5- [...] + <path fill="url(#w)" fill-rule="nonzero" d="M108.2 214.3c.1 0 .2-.1.3-.1-.1 0-.2 0-.3.1z"/> + <path fill="url(#x)" fill-rule="nonzero" d="M110.3 225.1l-.9-5.4c.1 0 .2-.1.2-.1.2-.1.5-.1.7-.2.8-.3 1.5-.8 2-1.4l.1-.1c1.4-1.6 2.1-3.9 2.5-5.8.1-.3-.1-.6-.5-.7-.3-.1-.6.1-.7.4-.2 1.2-.6 2.6-1.1 3.7v-.1c0 .1 0 .1-.1.2-.2-.5-.4-1-.5-1.4-.1-.2-.1-.3-.2-.4-.1-.3-.4-.5-.7-.4-.1 0-.1.1-.2.1-.1.2-.2.4-.1.6 0 .1.1.3.2.5.2.4.5 1 .6 1.6.2.6.1.9 0 .9l-.1.1c-.4.5-1 .9-1.6 1.1-.2.1-.3.1-.5.2h-.1l-.5-3.3c-.9.3-1.8.4-2.2.4h-.4c-.3 0-.5-.3-.5-.6 0-.1.1-.2.2-.3-.7 0-1.3-.2-2-.4h.1l.5 3s-.1 0-.1-.1c- [...] + <path fill="#EDEDF0" fill-rule="nonzero" d="M107.5 226.5h.4c.7-.1 1.4-.3 1.8-.5-1.2-.1-2.5-.1-3.8-.1v.1c.2.4.8.5 1.6.5z"/> + <path fill="url(#y)" fill-rule="nonzero" d="M107.5 226.5h.4c.7-.1 1.4-.3 1.8-.5-1.2-.1-2.5-.1-3.8-.1v.1c.2.4.8.5 1.6.5z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M105.2 203.6c.8-.3 1.5-.5 2.3-.8-.8.3-1.6.6-2.3.8zM102.8 204.3c-.7.2-1.5.3-2 .5.6-.2 1.3-.3 2-.5zM98.8 214s0 .1 0 0c.2.7.6 1.3.9 1.7h.1c-.5-.5-.8-1-1-1.7zM107.4 202.8c.7-.3 1.4-.6 1.9-.8-.5.2-1.2.5-1.9.8zM99.6 212.7s.1 0 0 0c.1.1.1 0 0 0z"/> + <path fill="#FFF" fill-rule="nonzero" d="M105.8 214.7c.1-.1.3-.2.4-.2 0 0 1 .1 2-.3.1 0 .2-.1.3-.1h.1c.4-.2.8-.5 1.2-.8l.1-.1.4-.4.5-.5c.1-.1.1-.2.2-.3.6-.9 1-2 1.1-3h.6c2 0 4-1 5.3-2.8 1.9-2.7 1.4-6.4-1-8.5-.1-.1-.3-.2-.5-.4-.3-.2-.6-.4-1-.6-.1 0-.1-.1-.2-.1.2-.2.4-.4.5-.6 1.8-2.6 1.2-6.1-1.4-7.9-.4-.3-.9-.5-1.3-.7-2.2-.7-4.8.1-6.2 2.1 0-.1-.1-.2-.1-.3-.1.1-.1.2-.2.2-.3-.8-.9-1.5-1.6-2-.8-.6-1.7-.8-2.7-.8-.3 0-.5.1-.8.1-1 .3-1.9.8-2.5 1.7-.2.3-.4.6-.5.9h-.2c0 .1-.1.1-.1.2-.6-.2-1.2- [...] + <path fill="url(#z)" fill-rule="nonzero" d="M203.6 209.1c.1.2.1.3.1.5.1 0 .2 0 .3.1-.1-.3-.3-.4-.4-.6z"/> + <path fill="url(#A)" fill-rule="nonzero" d="M227 221.9v-1.3-1.4-1.4-.7-1c-.7.3-1.5.5-2.2.5 0 2.6 0 4.6.1 6.2h2.2c-.1-.3-.1-.6-.1-.9z"/> + <path fill="url(#B)" fill-rule="nonzero" d="M203.3 223.1l-.2-.2-.5-.5c-.1-.1-.2-.2-.2-.3-.1-.2-.3-.3-.4-.5-.1-.1-.2-.2-.2-.3-.2-.2-.3-.4-.5-.5-.1-.1-.2-.2-.2-.3l-.6-.6c-.1-.1-.1-.2-.2-.2-.3-.3-.5-.6-.8-.9-.5-.6-1-1.2-1.5-1.9l-.1-.1-1.2-1.8c0-.1-.1-.1-.1-.2-.4-.6-.7-1.2-1.1-1.9 0-.1-.1-.1-.1-.2-.3-.6-.7-1.3-1-1.9 0-.1 0-.1-.1-.2-.3-.6-.5-1.1-.7-1.7-1-.4-2-.9-3-1.4 1.6 4.2 3.9 8.9 7.1 12.7 1.1 1.3 2 2.3 2.9 3.3.9-.1 1.9-.1 2.8-.2 0 0 0-.1-.1-.2z"/> + <path fill="url(#C)" fill-rule="nonzero" d="M204.7 212.6c0 .1 0 .1.1.2.1.4.2.8.4 1.2v.1c.1.4.2.7.3 1.1 0 .1.1.2.1.3.1.4.2.7.3 1.1v.1l.3 1.2c0 .1.1.2.1.3.1.3.2.6.2.9 0 .1.1.2.1.3.1.4.2.7.3 1.1 0 .1 0 .1.1.2.1.3.1.6.2.8 0 .1 0 .2.1.3.1.3.1.6.2.9V223c.7 0 1.5-.1 2.3-.1-.4-2.1-1.3-5.4-2.9-10.6-.8-.1-1.6-.3-2.5-.4.1.3.2.5.3.7z"/> + <path fill="url(#D)" fill-rule="nonzero" d="M214.9 199.4l8.1 2.3h.2c.4 0 .7-.2.8-.6.1-.4-.1-.9-.6-1l-8.1-2.3h-.2c-.4 0-.7.2-.8.6-.1.4.1.8.6 1z"/> + <path fill="url(#E)" fill-rule="nonzero" d="M267.5 198.4l-1.2-.6c-.4.3-.9.6-1.3.9.9-.1 1.7-.2 2.5-.3z"/> + <path fill="url(#F)" fill-rule="nonzero" d="M151.3 155.4c-1.2-.4-2.4-.6-3.6-.6-2.5 0-4.9.9-6.8 2.5l1.2-3.3c.1-.3 0-.7-.4-.8h-.2c-.3 0-.5.2-.6.4l-1.1 2.8v4.4l5.2 2h.2c.3 0 .5-.2.6-.4.1-.3 0-.7-.4-.8l-3.7-1.4c1.5-1.8 3.7-2.7 5.8-2.7 1.8 0 3.6.6 5.1 1.9 3.2 2.8 3.6 7.7.7 10.9-1.5 1.8-3.7 2.7-5.8 2.7-1.8 0-3.6-.6-5.1-1.9-1.7-1.5-2.7-3.6-2.7-5.9v2.5c0 1.1-.3 2.2-.9 3 1.9 2.8 5 4.6 8.6 4.6h.1c5.7-.1 10.3-4.7 10.2-10.4.2-4.2-2.4-8-6.4-9.5z"/> + <path fill="url(#G)" fill-rule="nonzero" d="M144.1 167.6l.8.3.1.2 3.3-1.5c.2-.1.3-.3.3-.4l1.8-4.8c.1-.3 0-.7-.4-.8h-.2c-.3 0-.5.2-.6.4l-1.7 4.6-3.1 1.3c-.3 0-.4.4-.3.7z"/> + <path fill="url(#H)" fill-rule="nonzero" d="M163 112.5c.1.5.7.5.8 0 1.2-4.8 5-8.6 9.8-9.8.5-.1.5-.7 0-.8-4.8-1.2-8.6-5-9.8-9.8-.1-.5-.7-.5-.8 0-1.2 4.8-5 8.6-9.8 9.8-.5.1-.5.7 0 .8 4.8 1.2 8.5 5 9.8 9.8z"/> + <path fill="url(#I)" fill-rule="nonzero" d="M234.8 212.7c-.1.5-.2 1.1-.3 1.6v.1c-.1.5-.2 1-.4 1.5-.1.5-.3.9-.4 1.4-.1.4-.3.8-.4 1.1 0 .1-.1.3-.1.4-.1.2-.2.5-.3.7-.1.1-.1.3-.2.4-.1.2-.2.4-.2.6-.1.1-.1.3-.2.4-.1.2-.2.4-.2.5-.1.1-.1.3-.2.4-.1.2-.2.3-.2.5-.1.1-.1.2-.2.3 0 0 0 .1-.1.1.8 0 1.7 0 2.5.1.8-1.7 1.5-3.5 2-4.9.6-1.7 1.1-4 1.6-6.9l-2.4.6c-.1.3-.1.6-.2 1l-.1.1z"/> + <path fill="url(#J)" fill-rule="nonzero" d="M191.9 204.4l-.3-.9c1.4 1.1 3.3 2 6 2.5h.1c.3 0 .5-.2.5-.5.1-.3-.1-.6-.4-.6-5.2-1-7.3-3.8-8.2-5.4-.1-.4-.3-.8-.4-1.2 0-.1-.1-.3-.1-.5l-.3-1.2c0-.2-.1-.4-.1-.5-.1-.4-.1-.8-.2-1.2v-.6-1c-.1.1-.2.1-.3.1-.1 0-.2 0-.3-.1-.5-.4-1.1-.7-1.6-1.1-.1 4.4.9 8.4 3 11.4l.3.9c1 .6 1.9 1.1 2.9 1.6-.3-.6-.4-1.2-.6-1.7z"/> + <path fill="url(#K)" fill-rule="nonzero" d="M178.1 85.3c-.1-.3-.5-.3-.6 0-.8 3.2-3.4 5.8-6.6 6.6-.3.1-.3.5 0 .6 3.2.8 5.8 3.4 6.6 6.6.1.3.5.3.6 0 .8-3.2 3.4-5.8 6.6-6.6.3-.1.3-.5 0-.6-3.3-.9-5.8-3.4-6.6-6.6z"/> + <path fill="url(#L)" fill-rule="nonzero" d="M180.4 204.7l3.1-.9-.9-3-3.1.9"/> + <path fill="url(#M)" fill-rule="nonzero" d="M176.8 200.9c-.9 0-1.9.4-2.5 1.2l-4.6 6.2-3.6-1.3-.1-.5c-.4-1.2-1.3-2.2-2.5-2.6l-.7-2.3-3.1.9.5 1.7c-2 1-2.8 3.4-1.8 5.4.7 1.4 2.1 2.2 3.6 2.2.6 0 1.2-.1 1.8-.4.6-.3 1.2-.8 1.5-1.4l2.3.8-1.7 2.2c-.3-.1-.7-.1-1-.1-1.8 0-3.4 1.2-3.9 3-.6 2.1.7 4.3 2.9 4.9.3.1.7.1 1 .1 1.8 0 3.4-1.2 3.9-3 .2-.7.2-1.5-.1-2.2-.1-.3-.1-.5-.2-.8l10.3-13.5c-.6-.3-1.3-.5-2-.5zm-13.9 8.8c-.1 0-.1 0-.2.1-.2.1-.5.1-.8.1-.8 0-1.6-.5-1.9-1.3-.4-1.1.1-2.3 1.2-2.7.2-.1.5-. [...] + <path fill="url(#N)" fill-rule="nonzero" d="M274.7 179.9c.4-1.1 1.2-2 2.3-2.4-.4-.2-.8-.3-1.2-.4-.3-.1-.6-.1-.9-.2-.2 1-.4 1.9-.8 3h.6z"/> + <path fill="url(#O)" fill-rule="nonzero" d="M180.9 206.3l.9 3.1c1.7-.5 2.6-2.3 2.1-4l-3 .9z"/> + <path fill="url(#P)" fill-rule="nonzero" d="M284.7 187.9c-.4-.2-.7-.3-1-.3-.7 0-1.3.3-1.6.9-1.7 3.1-4.9 4.9-8.3 4.9-.8 0-1.5-.1-2.3-.3-2.9-.7-5.3-2.8-6.4-5.6l3.7 1c.2 0 .3.1.5.1.8 0 1.6-.6 1.8-1.4.3-1-.3-2-1.4-2.3l-7.3-1.9c-.2 0-.3-.1-.5-.1-.8 0-1.6.6-1.8 1.4l-1.9 7.3c-.3 1 .3 2 1.4 2.3.2 0 .3.1.5.1.8 0 1.6-.6 1.8-1.4l.5-2c2.4 4.4 6.9 6.9 11.5 6.9 2.1 0 4.2-.5 6.1-1.5 2.3-1.3 4.3-3.2 5.5-5.6.5-.9.2-2-.8-2.5z"/> + <path fill="url(#Q)" fill-rule="nonzero" d="M172.7 212.8l2.1.8c.1-.1.3-.1.5-.1l3.8 1.3c.2 0 .3.2.3.3 1.3 0 2.6-.9 3-2.2l-7.7-2.7-2 2.6z"/> + <path fill="url(#R)" fill-rule="nonzero" d="M176.1 196l-.9-3-3.1.9 1 3"/> + <path fill="url(#S)" fill-rule="nonzero" d="M171.5 197.4l-.9-3.1-3.1 1 1 3"/> + <path fill="url(#T)" fill-rule="nonzero" d="M166.9 198.8l-.9-3.1-3.1 1 1 3"/> + <path fill="url(#U)" fill-rule="nonzero" d="M162.3 200.2l-.9-3.1c-1.7.5-2.6 2.3-2.1 4l3-.9z"/> + <path fill="url(#V)" fill-rule="nonzero" d="M274.7 180c-.2 0-.4-.1-.6-.1-.4 1.3-1 2.7-1.8 4.1.9 1 1.3 2.4 1 3.8-.3 1.3-1.3 2.3-2.5 2.8l.9.3c3-2.5 5.1-4.5 6.1-5.5l-.3-.1c-1.1-.3-2-1-2.5-1.9-.6-1-.7-2.1-.4-3.1 0-.1.1-.2.1-.3z"/> + <path fill="url(#W)" fill-rule="nonzero" d="M274.9 191.2c2.2-.3 4.2-1.7 5.3-3.7v-.1c.3-.4.6-.8 1-1.1l-1-.3s0 .1-.1.1c0 .2-1.9 2.2-5.2 5.1z"/> + <path fill="url(#X)" fill-rule="nonzero" d="M187.6 158.4c-1.4-.9-3.2-.6-4 .7-.8 1.3-.3 3 1.1 3.9 1.4.9 3.2.6 4-.7.8-1.2.3-3-1.1-3.9z"/> + <path fill="url(#Y)" fill-rule="nonzero" d="M276.7 136.6c-.3.7-.4 1.4-.4 2.1l-.9.3c-1.2.5-2.5 1.1-3.7 1.8-.2.1-.4.3-.7.4-.4.2-.7.5-1.2.8-.2.1-.4.3-.6.4l-.6.5c-.1.1-.3.2-.4.3h-.8c-1.1.1-9.8 2-18 3.9.6-1.9 1.2-3.8 1.6-5.8 1.3 0 2.7-.1 4-.2 1 .9 2.3 1.3 3.7 1.3 2.1 0 3.9-1.1 4.9-2.7.5.1 1 .1 1.5.1 4.8 0 9.1-3 10.7-7.5 3-1 5.2-3.7 5.7-6.9.6-.9 1.2-1.8 1.8-2.8 4-1.5 6.6-5.7 6-10 0-.4-.1-.7-.2-1.1 1.5-1.9 2.1-4.4 1.8-6.8-.3-2.1-1.2-4.1-2.7-5.6-.3-2.4-.8-4.7-1.5-7 .1-.4.1-.9.1-1.3 0-3.3-1.7 [...] + <path fill="#FFF" fill-rule="nonzero" d="M219.1 84.8c0-.6.1-1.3.3-1.8-.2.5-.3 1.2-.3 1.8z"/> + <path fill="url(#Z)" fill-rule="nonzero" d="M219.1 84.8c0-.6.1-1.3.3-1.8-.2.5-.3 1.2-.3 1.8z"/> + <path fill="url(#aa)" fill-rule="nonzero" d="M219.1 84.8c0-.6.1-1.3.3-1.8-.2.5-.3 1.2-.3 1.8z"/> + <path fill="url(#ab)" fill-rule="nonzero" d="M178.6 177.5c-.4-.2-.8-.3-1.1-.4l2.7 3.2c.2.2.2.6-.1.8-.1.1-.2.1-.4.1s-.3-.1-.4-.2l-3.3-4c-.9.1-1.6.6-2 1.3-.2.3-.3.7-.3 1.1 1.2 1.3 2.4 2.6 3.6 3.7 1.4.3 2.7-.2 3.3-1.2.7-1.4-.2-3.4-2-4.4z"/> + <path fill="url(#ac)" fill-rule="nonzero" d="M267.8 172.1c-2.3 1.3-4.3 3.2-5.5 5.6-.5.9-.1 2.1.8 2.5h.1l.4.1c.2 0 .3.1.5.1.7 0 1.4-.4 1.7-1.1 1.7-3 4.9-4.8 8.2-4.8.8 0 1.6.1 2.4.3 2.9.7 5.3 2.8 6.4 5.6l-3.7-1c-.2 0-.3-.1-.5-.1-.8 0-1.6.6-1.8 1.4-.3 1 .3 2 1.4 2.3l7.3 1.9c.2 0 .3.1.5.1.8 0 1.6-.6 1.8-1.4l1.9-7.3c.3-1-.3-2-1.4-2.3-.2 0-.3-.1-.5-.1-.8 0-1.6.6-1.8 1.4l-.5 2c-2.4-4.4-6.9-6.9-11.5-6.9-2.2.2-4.3.7-6.2 1.7z"/> + <path fill="url(#ad)" fill-rule="nonzero" d="M180.7 194.6c-.4-1.4-1.7-2.3-3.1-2.3-.3 0-.6 0-.9.1l.9 3.1 3.1-.9z"/> + <path fill="url(#ae)" fill-rule="nonzero" d="M172.6 172.1c.8-1.4.2-3.2-1.3-4-1.5-.8-3.3-.3-4.1 1.1-.8 1.4-.2 3.2 1.3 4 1.5.8 3.3.3 4.1-1.1z"/> + <path fill="url(#af)" fill-rule="nonzero" d="M175.8 164.2c1.7 0 3.1-1.3 3.1-2.9 0-1.6-1.4-2.9-3.1-2.9-1.7 0-3.1 1.3-3.1 2.9 0 1.6 1.4 2.9 3.1 2.9z"/> + <path fill="url(#ag)" fill-rule="nonzero" d="M224.1 117.1c.4 0 .9.1 1.3.1 0-.1.1-.2.1-.3v-3c0-.7-.6-1.3-1.3-1.3-.7 0-1.3.6-1.3 1.3v3c0 .1 0 .2.1.3.2-.1.7-.1 1.1-.1z"/> + <path fill="url(#ah)" fill-rule="nonzero" d="M237.5 199.2c.6-7.2 1.2-16.6 1.3-16.7 0-.3-.2-.6-.5-.6s-.6.2-.6.5c0 .2-1.4 19.4-1.9 23.9v.3c0 .2-.1.5-.1.7v.1c-.1.6-.2 1.1-.2 1.7v.2c.8-.2 1.6-.4 2.3-.6.1-.9.3-1.8.4-2.8 5.1-.6 12.8-2.1 20-5.6 2.3-1.3 4.3-2.6 6.2-3.9-.5-.4-1-.8-1.5-1.3-.8.7-1.8 1.2-2.9 1.2-.3 0-.7 0-1-.1-1.4.9-3 1.8-4.7 2.6-6.6 3-13 4.1-17.1 4.5.1-.9.2-1.8.2-2.9 1.8-.1 3.6-.2 5.5-.5 5.9-.9 10.7-2.9 14.2-4.8-.2-.2-.4-.5-.6-.8 0 0 0-.1-.1-.2-3.4 1.9-8 3.8-13.7 4.7-1.8.2-3.5. [...] + <path fill="url(#ai)" fill-rule="nonzero" d="M264.9 127.4c1 0 2.1-.3 3.3-1.3 2.4-2 2.5-4.3 2.3-5.6 1.4-.2 2.8-.8 4.2-2.2 3.6-3.7 2.9-7.8 2.1-9.4-.3-.5-1-.8-1.5-.5-.5.3-.8 1-.5 1.5 0 0 1.7 3.4-1.7 6.8-2.9 2.9-5.8 1.1-6.1.9-.5-.4-1.2-.2-1.6.3-.4.5-.2 1.2.3 1.6.8.5 2.1 1.1 3.7 1.2.2.9.2 2.9-1.9 4.7-2.6 2.1-4.8.3-4.9.2-.2-.2-.6-.2-.8.1-.2.2-.2.6.1.8 0-.2 1.2.9 3 .9z"/> + <path fill="url(#aj)" fill-rule="nonzero" d="M249.6 126.6c1 1 2.7 2.2 5.8 2.2.8 0 1.7-.1 2.7-.3.3-.1.5-.3.4-.6-.1-.3-.3-.5-.6-.4-4.9.9-6.7-1-7.4-1.7-1.3-1.3-1.9-4.5-1.9-4.6 0-.2-.2-.4-.4-.4-.1 0-5.1-1.5-8.5-5.7-3.1-3.9-3.5-8.4-3.5-8.4 0-.2-.1-.4-.3-.5-.8-.4-3.2-1.7-3.8-3.2-1.3-3.1.1-4.9.1-4.9.2-.2.2-.6-.1-.8-.2-.2-.6-.2-.8.1 0 0-.5.6-.8 1.7l-3.3-.1c-.3 0-.6.2-.6.5v.2c.1.2.3.4.5.4l3.2.1c0 .9.1 2 .6 3.2.4.9 1.2 1.7 2 2.3.8.6 1.6 1.1 2.1 1.3 0 .3.1.8.3 1.4.4 1.8 1.3 4.7 3.5 7.3.4.5.8 1 [...] + <path fill="url(#ak)" fill-rule="nonzero" d="M179 200.2l3.1-1-.9-3-3.1.9"/> + <path fill="url(#al)" fill-rule="nonzero" d="M231.2 188.3l-16.2-4.7c-.3-.1-.6-.1-.9-.1-1.5 0-2.8 1-3.2 2.4l-5.6 19.4c-.5 1.8.5 3.7 2.3 4.2l16.2 4.7c.3.1.6.1.9.1 1.5 0 2.8-1 3.2-2.4l5.6-19.4c.5-1.8-.5-3.7-2.3-4.2zm-1.4 4.9l-4.7 16.2c-.2.7-.9 1.2-1.6 1.2-.2 0-.3 0-.5-.1l-12.9-3.7c-.9-.3-1.4-1.2-1.2-2.1l4.7-16.2c.2-.7.9-1.2 1.6-1.2.2 0 .3 0 .5.1l12.9 3.7c.9.2 1.5 1.2 1.2 2.1z"/> + <path fill="url(#am)" fill-rule="nonzero" d="M99.3 199.1c-.2-.6-.9-1-1.5-.7-.6.2-1 .9-.7 1.5l.8 2.4c.6-.6 1.4-.9 2.2-.8l-.8-2.4z"/> + <path fill="url(#an)" fill-rule="nonzero" d="M224.4 196.8l-8.1-2.3h-.2c-.4 0-.7.2-.8.6-.1.4.1.9.6 1l8.1 2.3h.2c.4 0 .7-.2.8-.6.1-.4-.2-.8-.6-1z"/> + <path fill="url(#ao)" fill-rule="nonzero" d="M108 198.9c.6-.6 1.4-.9 2.2-.8l-.8-2.4c-.2-.6-.9-1-1.5-.7-.6.2-1 .9-.7 1.5l.8 2.4z"/> + <path fill="url(#ap)" fill-rule="nonzero" d="M106.2 206.7c1-.5 2-1 3.1-.7.1-.1.3-.2.4-.4.5-.5.9-1.1 1.3-1.8.2-.4.4-.7.6-1.1.2-.4.3-.9.5-1.4l.1-.2v-.1c0-.1-.1-.2-.2-.1-.2.1-.5.1-.7.2-.3.1-.7.2-1 .3l-1.7.5c-1.2.3-2.3.7-3.5 1.1-1.1.4-2.3.8-3.4 1.2l-1.7.6c-.3.1-.6.3-1 .4-.2.1-.5.2-.7.3 0 0-.1 0-.1.1-.1.1 0 .2 0 .3l.2.1c.4.3.8.6 1.2.8.4.2.8.4 1.1.6.7.3 1.4.5 2.1.6.2 0 .4 0 .5.1.1-.1.1-.2.2-.2.7-.9 1.7-1 2.7-1.2zm-5.4-1.9c.6-.1 1.3-.3 2-.5-.7.2-1.4.3-2 .5zm6.6-2c.7-.3 1.4-.6 1.9-.8-.5.2-1. [...] + <path fill="url(#aq)" fill-rule="nonzero" d="M225.3 193.6l-8.1-2.3h-.2c-.4 0-.7.2-.8.6-.1.4.1.9.6 1l8.1 2.3h.2c.4 0 .7-.2.8-.6.1-.4-.2-.9-.6-1z"/> + <path fill="url(#ar)" fill-rule="nonzero" d="M164 84.3c-.2 0-.2.3 0 .3 1.8.5 3.3 1.9 3.7 3.7 0 .2.3.2.3 0 .5-1.8 1.9-3.3 3.7-3.7.2 0 .2-.3 0-.3-1.6-.4-2.9-1.6-3.5-3.1h-.7c-.6 1.5-1.9 2.7-3.5 3.1z"/> + <path fill="url(#as)" fill-rule="nonzero" d="M213.9 202.6l3.2.9h.2c.4 0 .7-.2.8-.6.1-.4-.1-.9-.6-1l-3.2-.9h-.2c-.4 0-.7.2-.8.6-.1.4.2.9.6 1z"/> + <path fill="url(#at)" fill-rule="nonzero" d="M227.9 119.2l-.3-.3c-.1-.1-.2-.2-.3-.2-.7-.5-1.8-1.1-3.2-1.1-2.9 0-4.4 2.2-4.5 2.3-.3.5-.2 1.2.3 1.5.5.3 1.2.2 1.5-.3 0 0 .9-1.3 2.6-1.3 1.1 0 1.9.5 2.3.9l.2.2.2.2c.2.3.6.5.9.5.2 0 .4-.1.6-.2.5-.3.7-1 .3-1.5.1 0-.1-.3-.6-.7z"/> + <path fill="url(#au)" fill-rule="nonzero" d="M196.8 121.5c.5.3 1.2.2 1.5-.3 0 0 .1-.2.4-.4.4-.4 1.2-.9 2.2-.9 1.7 0 2.6 1.3 2.6 1.3.2.3.6.5.9.5.2 0 .4-.1.6-.2.5-.3.7-1 .3-1.5-.1-.1-1.5-2.3-4.5-2.3-1.9 0-3.2 1-3.9 1.7l-.3.3c-.2.2-.3.4-.3.4-.2.4 0 1.1.5 1.4z"/> + <path fill="url(#av)" fill-rule="nonzero" d="M200.9 117.1c.4 0 .9.1 1.3.1 0-.1.1-.2.1-.3v-3c0-.7-.6-1.3-1.3-1.3-.7 0-1.3.6-1.3 1.3v3c0 .1 0 .2.1.3.3-.1.7-.1 1.1-.1z"/> + <path fill="url(#aw)" fill-rule="nonzero" d="M207.7 137.3l-1.5-.6c-.7-.3-1.5-.5-2.2-.8l-3.8-1.3-7.5-2.4c-.2-.1-.4-.1-.6-.2-1.1 1.2-2.3 2.4-2.8 2.7-.4.2-3.4-.5-3.5-.8-.1-.2.3-1.9.7-3.5-.5-.1-.9-.3-1.4-.4l-.6-.1c-.3 1.4-.7 3.1-.9 3.2-.4.3-2.9-1.2-3.1-1.5-.1-.2-.5-1.6-.7-2.9-.3-.1-.5-.1-.8-.2-.5-.1-1.1-.2-1.6-.4h-.2c-.2.1-.3.3-.3.5l.1.5c.3 1.1.7 2.2 1.2 3 .4.9.9 1.7 1.3 2.5.9 1.5 1.9 2.7 3 3.8.3.3.6.5.9.8.2-.1.4-.1.6-.1-.2 0-.4.1-.6.1 1.9 1.6 3.9 2.7 6 3.3h.2c2.2.6 4.4.8 6.9.5.4 0 .8-.1 [...] + <path fill="url(#ax)" fill-rule="nonzero" d="M231.2 80.2c.4 0 .6-.2.6-.5 0-.1.5-3 4.2-3.5 1.8-.3 2.9.5 3.5 1.2-3.5 2.2-4.1 5.7-3.9 7.3.1.6.6 1 1.1 1h.1c.6-.1 1-.6 1-1.2 0 0-.4-3.8 4-5.8 3.8-1.7 5.8 1 6.1 1.4.3.5 1 .6 1.5.3s.6-1 .3-1.5c-.5-.7-1.3-1.5-2.5-2.1.5-.9 1.6-2.1 3.8-2.4 3.3-.5 4.2 2.3 4.3 2.4.1.3.4.5.7.4.3-.1.5-.4.4-.7 0 0-1.3-3.8-5.5-3.2-2.8.4-4.1 2-4.7 3.1-1.5-.5-3.3-.5-5.3.4-.1.1-.3.1-.4.2-.8-1-2.2-2.1-4.7-1.7-4.5.6-5.1 4.4-5.2 4.4 0 .2.3.5.6.5z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M207.7 223.7v.2c0 .2.1.4.1.6v.2c0 .2.1.4.1.6v.5c0 .1 0 .2-.1.2l-.2.2H207.3h-.1-.1c-.1 0-.1 0-.2-.1h-.1c-.1 0-.2-.1-.3-.2 0 0-.1 0-.1-.1-.1-.1-.2-.1-.3-.2l-.1-.1c-.1-.1-.2-.1-.3-.2-.1 0-.1-.1-.2-.1l-.3-.3-.2-.2-.3-.3-.2-.2-.5-.5-.1-.1-.4-.4c-1 .1-1.9.1-2.8.2 3.3 3.6 5 4.9 6.6 4.9.9 0 1.8-.4 2.3-1.2.4-.6.8-1.1.2-4.3-.8 0-1.5.1-2.3.1.1.4.1.6.2.8z"/> + <path fill="url(#ay)" fill-rule="nonzero" d="M207.7 223.7v.2c0 .2.1.4.1.6v.2c0 .2.1.4.1.6v.5c0 .1 0 .2-.1.2l-.2.2H207.3h-.1-.1c-.1 0-.1 0-.2-.1h-.1c-.1 0-.2-.1-.3-.2 0 0-.1 0-.1-.1-.1-.1-.2-.1-.3-.2l-.1-.1c-.1-.1-.2-.1-.3-.2-.1 0-.1-.1-.2-.1l-.3-.3-.2-.2-.3-.3-.2-.2-.5-.5-.1-.1-.4-.4c-1 .1-1.9.1-2.8.2 3.3 3.6 5 4.9 6.6 4.9.9 0 1.8-.4 2.3-1.2.4-.6.8-1.1.2-4.3-.8 0-1.5.1-2.3.1.1.4.1.6.2.8z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M231.2 223.1c-.1.1-.1.2-.2.3-.1.2-.2.3-.3.4 0 .1-.1.2-.1.2-.1.2-.2.4-.4.5v.1c-.1.2-.3.4-.4.5 0 .1-.1.1-.1.1-.1.1-.2.2-.2.3 0 .1-.1.1-.1.1l-.2.2-.1.1c-.1.1-.1.1-.2.1l-.1.1c-.1 0-.1.1-.2.1 0 0-.1 0-.1.1h-.2-.1-.1-.1c-.1 0-.2-.1-.2-.1l-.1-.1c-.1-.1-.1-.3-.2-.6s-.1-.6-.1-1c0-.3-.1-.7-.1-1.1v-.9h-2.2c.2 4.5.8 4.9 1.4 5.4.5.4 1.2.6 1.8.6 2.3 0 4.3-2.8 5.8-5.9-.8 0-1.6 0-2.5-.1-.3.4-.4.5-.4.6z"/> + <path fill="url(#az)" fill-rule="nonzero" d="M231.2 223.1c-.1.1-.1.2-.2.3-.1.2-.2.3-.3.4 0 .1-.1.2-.1.2-.1.2-.2.4-.4.5v.1c-.1.2-.3.4-.4.5 0 .1-.1.1-.1.1-.1.1-.2.2-.2.3 0 .1-.1.1-.1.1l-.2.2-.1.1c-.1.1-.1.1-.2.1l-.1.1c-.1 0-.1.1-.2.1 0 0-.1 0-.1.1h-.2-.1-.1-.1c-.1 0-.2-.1-.2-.1l-.1-.1c-.1-.1-.1-.3-.2-.6s-.1-.6-.1-1c0-.3-.1-.7-.1-1.1v-.9h-2.2c.2 4.5.8 4.9 1.4 5.4.5.4 1.2.6 1.8.6 2.3 0 4.3-2.8 5.8-5.9-.8 0-1.6 0-2.5-.1-.3.4-.4.5-.4.6z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M167.7 80.6c-.1.2-.1.4-.2.6h.7c-.1-.2-.2-.4-.2-.6 0-.2-.3-.2-.3 0z"/> + <path fill="url(#aA)" fill-rule="nonzero" d="M167.7 80.6c-.1.2-.1.4-.2.6h.7c-.1-.2-.2-.4-.2-.6 0-.2-.3-.2-.3 0z"/> + <path fill="#FFF" fill-rule="nonzero" d="M118.4 58.7c.1.2.2.3.4.3h.1c.3-.1.4-.3.4-.6v-.1l-1.9-5.3c-.1-.3-.4-.4-.6-.3-.3.1-.4.4-.3.6l1.9 5.4z"/> + <path fill="url(#aB)" fill-rule="nonzero" d="M118.4 58.7c.1.2.2.3.4.3h.1c.3-.1.4-.3.4-.6v-.1l-1.9-5.3c-.1-.3-.4-.4-.6-.3-.3.1-.4.4-.3.6l1.9 5.4z"/> + <path fill="#FFF" fill-rule="nonzero" d="M134.3 121.9c.2-.2.5-.4.9-.2v.1l2.4-1.5v-3.5l-3.4 2.1v3h.1zM137.7 164.3c-.2.2-.4.6-.4.9 0 .9.1 1.8.4 2.6v-3.5z"/> + <path fill="url(#aC)" fill-rule="nonzero" d="M137.7 164.3c-.2.2-.4.6-.4.9 0 .9.1 1.8.4 2.6v-3.5z"/> + <path fill="#FFF" fill-rule="nonzero" d="M115.5 59c.3 0 .5-.2.5-.5v-5.3c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.3c0 .3.2.5.5.5z"/> + <path fill="url(#aD)" fill-rule="nonzero" d="M115.5 59c.3 0 .5-.2.5-.5v-5.3c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.3c0 .3.2.5.5.5z"/> + <path fill="#FFF" fill-rule="nonzero" d="M112.6 59c.3 0 .5-.2.5-.5v-5.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.8c0 .3.2.5.5.5z"/> + <path fill="url(#aE)" fill-rule="nonzero" d="M112.6 59c.3 0 .5-.2.5-.5v-5.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.8c0 .3.2.5.5.5z"/> + <path fill="#FFF" fill-rule="nonzero" d="M114 59c.3 0 .5-.2.5-.5v-4.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v4.8c0 .3.3.5.5.5z"/> + <path fill="url(#aF)" fill-rule="nonzero" d="M114 59c.3 0 .5-.2.5-.5v-4.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v4.8c0 .3.3.5.5.5z"/> + <path fill="#FFF" fill-rule="nonzero" d="M129.1 125.6l4.1-2.6v-.5c0-.1-.1-.1-.1-.2 0 0 .1 0 .1.1v-2.9l-5.6 3.6c-.8.3-1.1 1.2-.8 2 .2.6.8.9 1.3.9.2 0 .4 0 .6-.1.1-.1.2-.2.4-.3z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M139 115.8l-1.3.9v3.5l2.2-1.4v-8.3c-.5.7-.9 1.5-1.1 2.3-.1.5-.1 1-.1 1.4.1.6.1 1.1.3 1.6zM139.9 165.2c0-.7-.6-1.3-1.3-1.3-.4 0-.7.1-.9.4v3.5c.3 1.1.7 2.1 1.3 3 .6-.9.9-1.9.9-3v-2.6z"/> + <path fill="url(#aG)" fill-rule="nonzero" d="M139.9 165.2c0-.7-.6-1.3-1.3-1.3-.4 0-.7.1-.9.4v3.5c.3 1.1.7 2.1 1.3 3 .6-.9.9-1.9.9-3v-2.6z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M138.7 159.7c-.1.3 0 .7.4.8l.8.3v-4.4l-1.2 3.3z"/> + <path fill="url(#aH)" fill-rule="nonzero" d="M138.7 159.7c-.1.3 0 .7.4.8l.8.3v-4.4l-1.2 3.3z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M198 217c.5.6.9 1.3 1.5 1.9-.5-.7-1-1.3-1.5-1.9z"/> + <path fill="url(#aI)" fill-rule="nonzero" d="M198 217c.5.6.9 1.3 1.5 1.9-.5-.7-1-1.3-1.5-1.9z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M207.8 226l-.2.2c.1-.1.2-.1.2-.2z"/> + <path fill="url(#aJ)" fill-rule="nonzero" d="M207.8 226l-.2.2c.1-.1.2-.1.2-.2z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M224.1 117.6c1.4 0 2.5.5 3.2 1.1-.7-.5-1.8-1.1-3.2-1.1z"/> + <path fill="url(#aK)" fill-rule="nonzero" d="M224.1 117.6c1.4 0 2.5.5 3.2 1.1-.7-.5-1.8-1.1-3.2-1.1z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M224.1 119.9c1.1 0 1.9.5 2.3.9-.4-.4-1.2-.9-2.3-.9z"/> + <path fill="url(#aL)" fill-rule="nonzero" d="M224.1 119.9c1.1 0 1.9.5 2.3.9-.4-.4-1.2-.9-2.3-.9z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M134.3 121.9v-3.1l-1.1.7v2.9s-.1 0-.1-.1c0 .1.1.1.1.2v.5l2.1-1.3v-.1c-.5-.1-.8 0-1 .3z"/> + <path fill="url(#aM)" fill-rule="nonzero" d="M150.7 112.6l-4.5 2.7c-.3.2-.7.3-1 .3-.6 0-1.3-.3-1.6-.9-.4-.6-.4-1.3-.1-1.8.1-.4.4-.7.8-1l4.2-2.7c-.7-.5-1.5-.9-2.3-1.1-.4-.1-.8-.1-1.3-.1-2 0-3.8 1-4.9 2.5-.5.7-.9 1.5-1.1 2.3-.1.5-.1 1-.1 1.4 0 .5.1 1 .3 1.5v.1l-1.3.8-3.4 2.1-1.1.7-5.6 3.6c-.8.3-1.1 1.2-.8 2 .2.6.8.9 1.3.9.2 0 .4 0 .6-.1.1-.1.3-.1.4-.2l4.1-2.6 2.1-1.3 2.4-1.5 2.2-1.4.7-.5c.8.8 1.7 1.3 2.8 1.6.5.1.9.2 1.4.2 1.4 0 2.7-.5 3.7-1.3s1.8-2 2.1-3.4c.2-1 .2-1.9 0-2.8z"/> + <path fill="#FFFEFE" fill-rule="nonzero" d="M143.5 114.7c.4.6 1 .9 1.6.9.4 0 .7-.1 1-.3l4.5-2.7c.2.9.2 1.9 0 2.8-.3 1.4-1.1 2.6-2.1 3.4 1.1-.8 1.9-2.1 2.2-3.5.2-.9.2-1.9 0-2.8l-4.6 2.7c-.3.2-.7.3-1 .3-.6 0-1.3-.3-1.7-.9-.3-.5-.4-1.2-.2-1.7-.1.5-.1 1.2.3 1.8z"/> + <path fill="url(#aN)" fill-rule="nonzero" d="M143.5 114.7c.4.6 1 .9 1.6.9.4 0 .7-.1 1-.3l4.5-2.7c.2.9.2 1.9 0 2.8-.3 1.4-1.1 2.6-2.1 3.4 1.1-.8 1.9-2.1 2.2-3.5.2-.9.2-1.9 0-2.8l-4.6 2.7c-.3.2-.7.3-1 .3-.6 0-1.3-.3-1.7-.9-.3-.5-.4-1.2-.2-1.7-.1.5-.1 1.2.3 1.8z"/> + <path fill="#FFFEFE" fill-rule="nonzero" d="M126.8 125.1c-.3-.8 0-1.7.8-2l5.6-3.6 1.1-.7 3.4-2.1 1.3-.8v-.1l-11.5 7.3c-.8.3-1.1 1.3-.8 2 .3.6.8.9 1.4.9h.1c-.7 0-1.2-.3-1.4-.9z"/> + <path fill="url(#aO)" fill-rule="nonzero" d="M126.8 125.1c-.3-.8 0-1.7.8-2l5.6-3.6 1.1-.7 3.4-2.1 1.3-.8v-.1l-11.5 7.3c-.8.3-1.1 1.3-.8 2 .3.6.8.9 1.4.9h.1c-.7 0-1.2-.3-1.4-.9z"/> + <path fill="#FFFEFE" fill-rule="nonzero" d="M139.9 110.5c1.1-1.5 2.9-2.5 4.9-2.5.4 0 .8 0 1.3.1.8.2 1.6.6 2.3 1.1l.2-.1c-.7-.6-1.5-1-2.4-1.2-.4-.1-.8-.1-1.3-.1-2.8 0-5.4 2-6 4.9-.1.5-.1 1-.1 1.6 0-.5 0-1 .1-1.4.1-1 .5-1.7 1-2.4z"/> + <path fill="url(#aP)" fill-rule="nonzero" d="M139.9 110.5c1.1-1.5 2.9-2.5 4.9-2.5.4 0 .8 0 1.3.1.8.2 1.6.6 2.3 1.1l.2-.1c-.7-.6-1.5-1-2.4-1.2-.4-.1-.8-.1-1.3-.1-2.8 0-5.4 2-6 4.9-.1.5-.1 1-.1 1.6 0-.5 0-1 .1-1.4.1-1 .5-1.7 1-2.4z"/> + <path fill="url(#aQ)" fill-rule="nonzero" d="M106.4 207.6c.1 0 .1 0 0 0h.1c1-.3 1.9-.9 2.7-1.6-1.1-.3-2 .2-3.1.7-1 .2-2 .3-2.7 1l-.2.2c1.2.2 2.3 0 3.2-.3z"/> + <path fill="url(#aR)" fill-rule="nonzero" d="M118.3 196.1c.7-1.4.9-3 .6-4.6-.4-2.1-1.5-3.9-3.3-5.1-1.4-1-3-1.4-4.6-1.4-1.5 0-3 .5-4.2 1.3-.2-.2-.4-.3-.6-.5-1.2-.8-2.5-1.2-4-1.2-2.1 0-4 1-5.3 2.6h-.7c-2.7 0-5.2 1.4-6.8 3.6-1.8 2.6-1.9 5.9-.6 8.6-.7.5-1.2 1.1-1.7 1.8-1.3 1.8-1.8 4.1-1.4 6.3.4 2.2 1.6 4.1 3.5 5.4 1.2.9 2.6 1.4 4.1 1.5.4 1.1 1.2 2.1 2.2 2.8 1 .7 2.2 1.1 3.5 1.1 1 .9 2.2 1.6 3.7 2.2l1 5.5h2.3l-1.3-7.2c-1.3-.4-2.7-1-3.8-1.8-.4-.3-.7-.6-1-1h-.1l-.1-.1c-.4-.4-.7-1-.9-1.6v-.1 [...] + <path fill="#EDEDF0" fill-rule="nonzero" d="M107.9 226.5h-.4c-.8 0-1.5-.2-1.5-.6v-.1h-2.3l.1.5c.2 1.2 1.3 2.5 3.8 2.5 1.5 0 3.5-.5 4.6-1.8.2-.2.3-.5.4-.7l-2.7-.3c-.5.2-1.3.4-2 .5z"/> + <path fill="url(#aS)" fill-rule="nonzero" d="M107.9 226.5h-.4c-.8 0-1.5-.2-1.5-.6v-.1h-2.3l.1.5c.2 1.2 1.3 2.5 3.8 2.5 1.5 0 3.5-.5 4.6-1.8.2-.2.3-.5.4-.7l-2.7-.3c-.5.2-1.3.4-2 .5z"/> + <path d="M-30-28h352v303H-30z"/> + </g> +</svg> diff --git a/browser/extensions/onboarding/content/img/figure_default.svg b/browser/extensions/onboarding/content/img/figure_default.svg new file mode 100644 index 0000000000000..c52e4b8500f7b --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_default.svg @@ -0,0 +1 @@ +<svg width="272" height="247" viewBox="0 0 272 247" xmlns="http://www.w3.org/2000/svg"><title>default-browser</title><defs><linearGradient x1="-12.708%" y1="-28.803%" x2="102.994%" y2="115.824%" id="a"><stop stop-color="#FFCCD7" offset="40.06%"/><stop stop-color="#EDBEE2" offset="100%"/></linearGradient><linearGradient x1="-78.121%" y1="-55.724%" x2="136.609%" y2="135.651%" id="b"><stop stop-color="#FFE900" offset="28.07%"/><stop stop-color="#FFCC07" offset="32.21%"/><stop stop-color="#F [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_library.svg b/browser/extensions/onboarding/content/img/figure_library.svg new file mode 100644 index 0000000000000..aad20181b9964 --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_library.svg @@ -0,0 +1,689 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="267" height="240"> + <defs> + <linearGradient id="a" x1="-287.251713%" x2="363.382118%" y1="-127.999431%" y2="247.172106%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="-8347.28%" x2="11424.26%" y1="-8337.33%" y2="11434.21%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="c" x1="-2354.3122%" x2="2468.01463%" y1="-738.5544%" y2="843.1688%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="d" x1="-11316.73%" x2="8454.81%" y1="-5346.60952%" y2="4068.40952%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="e" x1="-156.148629%" x2="205.305484%" y1="-480.49483%" y2="430.938303%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="f" x1="-11777.11%" x2="7994.43%" y1="-1542.90541%" y2="1128.92432%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="g" x1="-1966.10678%" x2="1385.00169%" y1="-2646.49545%" y2="1847.03636%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="h" x1="-1259.26087%" x2="945.558937%" y1="-1283.95691%" y2="942.373333%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="i" x1="-4828.28387%" x2="3895.46452%" y1="-2550.56897%" y2="2112.12414%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="j" x1="-1420.34388%" x2="1159.68716%" y1="-3565.4194%" y2="2819.67133%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="k" x1="-6578.28%" x2="13193.26%" y1="-6566.33%" y2="13205.21%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="l" x1="-690.589109%" x2="1266.98911%" y1="-1068.60597%" y2="1882.37015%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="m" x1="-3693.78418%" x2="6240.18862%" y1="-1360.99327%" y2="2373.67085%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="n" x1="-51.4002563%" x2="99.3496099%" y1="-59.6430664%" y2="133.087695%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="o" x1="-47.4074974%" x2="121.810771%" y1="-106.87209%" y2="132.306567%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="p" x1="-701.943676%" x2="609.202314%" y1="-537.964802%" y2="487.22249%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="q" x1="-1074.53%" x2="834.91%" y1="-358.218519%" y2="348.981481%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="r" x1="-5230.64688%" x2="3222.21875%" y1="-2856.73793%" y2="1806.91207%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="s" x1="-1536.40601%" x2="955.898444%" y1="-3896.2795%" y2="2345.49035%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="t" x1="-2573.03736%" x2="4141.82528%" y1="-7694%" y2="12077.54%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="u" x1="-105.756%" x2="253.726545%" y1="-959.543678%" y2="1313.04713%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="v" x1="-113.495628%" x2="246.641894%" y1="-1951.93556%" y2="2441.74%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="w" x1="-203.741261%" x2="362.77851%" y1="-8794.04%" y2="10977.5%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="x" x1="-8901.65455%" x2="9072.47273%" y1="-4629.9%" y2="4785.11905%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="y" x1="-135.885507%" x2="273.463147%" y1="-6854.87692%" y2="8354%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="z" x1="-237.240755%" x2="222.496119%" y1="-950.902381%" y2="659.16369%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="A" x1="-323.294457%" x2="276.418625%" y1="-16784.12%" y2="10262.94%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="B" x1="-324.50885%" x2="273.863496%" y1="-16876.15%" y2="10170.29%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="C" x1="-8757.43409%" x2="-13250.9636%" y1="-25788.3267%" y2="-38969.3533%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="D" x1="-4977.81154%" x2="-7512.62308%" y1="-21732.3667%" y2="-32716.5611%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="E" x1="-778.197863%" x2="-1200.66709%" y1="-2873.70382%" y2="-4382.98244%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="F" x1="-3162.7925%" x2="-4810.42083%" y1="-25654.4533%" y2="-38835.4867%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="G" x1="-1053.32338%" x2="1514.40909%" y1="-4984.71765%" y2="6645.6%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="H" x1="-5039.72338%" x2="-7607.45714%" y1="-23040.7706%" y2="-34671.0941%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="I" x1="143.631333%" x2="-4.86%" y1="790.352632%" y2="-381.952632%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="J" x1="-2552.41333%" x2="-3870.516%" y1="-20494.2053%" y2="-30900.2789%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="K" x1="-1250.60304%" x2="-1918.56115%" y1="-38487.33%" y2="-58258.87%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="L" x1="-37598.9%" x2="-57370.44%" y1="-17879.1857%" y2="-27294.2048%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="M" x1="-882.727251%" x2="-1363.78637%" y1="-29434.6846%" y2="-44643.5692%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="N" x1="-268.313828%" x2="273.677355%" y1="-882.118713%" y2="699.481287%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="O" x1="-420.455862%" x2="943.098621%" y1="-4784.28571%" y2="9338.24286%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="P" x1="-587.656122%" x2="1429.84796%" y1="-3859.74375%" y2="8497.475%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="Q" x1="-597.567708%" x2="1461.96771%" y1="-6217.96%" y2="13553.58%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="R" x1="-989.3%" x2="1835.20571%" y1="-6563.19091%" y2="11410.9364%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="S" x1="-1683.03158%" x2="3520.00526%" y1="-4061.93125%" y2="8295.28125%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="T" x1="-289.56383%" x2="551.778298%" y1="-736.619802%" y2="1220.95842%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="U" x1="-8102.24%" x2="11669.3%" y1="-8112.37%" y2="11659.17%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="V" x1="-527.27218%" x2="959.309774%" y1="-7671.89%" y2="12099.65%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="W" x1="-563.298261%" x2="1155.96609%" y1="-4360.425%" y2="7996.7875%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="X" x1="-595.656881%" x2="1218.24587%" y1="-7031.95%" y2="12739.59%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="Y" x1="-4261.16471%" x2="7369.15294%" y1="-5186.16429%" y2="8936.36429%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="Z" x1="-7291.52%" x2="12480.03%" y1="-7323.1%" y2="12448.44%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aa" x1="-46.8866667%" x2="106.777333%" y1="-610.354545%" y2="437.354545%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="ab" x1="-954.992%" x2="1681.21333%" y1="-6801.97273%" y2="11172.1545%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ac" x1="-53.1965517%" x2="108.827586%" y1="-138.8375%" y2="154.825%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="ad" x1="-2268.40345%" x2="4549.36897%" y1="-4153.9%" y2="8203.3125%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ae" x1="-134.196822%" x2="349.214914%" y1="-7485.96%" y2="12285.58%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="af" x1="-203.129153%" x2="467.092542%" y1="-7412.3%" y2="12359.24%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ag" x1="-8254.16%" x2="11517.38%" y1="-4829.67647%" y2="6800.64118%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ah" x1="-261.207831%" x2="281.860241%" y1="-1137.19462%" y2="943.173846%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="ai" x1="-353.298433%" x2="352.892428%" y1="-15403.61%" y2="11643.5%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aj" x1="-355.267885%" x2="350.914099%" y1="-15487.8%" y2="11558.97%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="ak" x1="-2084.69358%" x2="-3141.99572%" y1="-5548.86479%" y2="-8333.58732%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="al" x1="-2136.94011%" x2="-3223.28791%" y1="-39758.41%" y2="-59529.95%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="am" x1="-8671.43111%" x2="-13065.1111%" y1="-39159.26%" y2="-58930.8%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="an" x1="42.05%" x2="39.02%" y1="40.85%" y2="37.83%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ao" x1="-1655.02189%" x2="-2503.58541%" y1="-18008.5045%" y2="-26995.5636%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ap" x1="26.16%" x2="23.82%" y1="17.93%" y2="15.58%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aq" x1="-7321.04%" x2="-10915.8655%" y1="-26976.66%" y2="-40157.6867%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ar" x1="-3806.45143%" x2="-5689.45619%" y1="-33702.4583%" y2="-50178.75%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="as" x1="-719.07449%" x2="1298.42959%" y1="-4375.10588%" y2="7255.21176%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="at" x1="-4193.87653%" x2="-6211.37959%" y1="-24406.3118%" y2="-36036.6294%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="au" x1="-524.679508%" x2="1095.93852%" y1="-4333.45%" y2="8023.7625%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="av" x1="-3315.91393%" x2="-4936.53115%" y1="-25616.6063%" y2="-37973.8188%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aw" x1="-1422.94082%" x2="2612.06735%" y1="-5115.85714%" y2="9006.67143%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ax" x1="-8372.54082%" x2="-12407.5531%" y1="-29439.4643%" y2="-43561.9929%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="ay" x1="-2040.6303%" x2="3950.74545%" y1="-6860.53%" y2="12911.01%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="az" x1="-12359.7364%" x2="-18351.1091%" y1="-40913.58%" y2="-60685.12%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aA" x1="-1005.75152%" x2="1989.93788%" y1="-6296.96364%" y2="11677.1727%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aB" x1="-6165.30303%" x2="-9160.98939%" y1="-37254.2727%" y2="-55228.4%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aC" x1="-2871.84%" x2="5036.776%" y1="-4515.63125%" y2="7841.58125%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aD" x1="-16493.056%" x2="-24401.672%" y1="-25798.7875%" y2="-38156%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aE" x1="-4836.46667%" x2="8344.56%" y1="-7269.91%" y2="12501.63%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aF" x1="-27538.4933%" x2="-40719.52%" y1="-41322.96%" y2="-61094.5%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aG" x1="123.979381%" x2="7.09896907%" y1="645.125%" y2="-299.65%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aH" x1="-4143.41443%" x2="-6181.71959%" y1="-33849.65%" y2="-50325.925%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aI" x1="110.22963%" x2="13.6574074%" y1="263.406667%" y2="-84.2533333%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aJ" x1="-7493.57037%" x2="-11154.9667%" y1="-27110.28%" y2="-40291.3067%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aK" x1="-1314.06588%" x2="-1982.02331%" y1="-40374.36%" y2="-60145.89%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aL" x1="-39504.49%" x2="-59276.05%" y1="-23215.4176%" y2="-34845.7353%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aM" x1="-935.697066%" x2="-1419.10856%" y1="-40260.71%" y2="-60032.24%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aN" x1="-239.365731%" x2="302.59479%" y1="-1057.81832%" y2="1006.59618%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aO" x1="-195.98196%" x2="188.238494%" y1="-262.20413%" y2="218.292299%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aP" x1="-148.239568%" x2="156.504317%" y1="-236.10625%" y2="205.1375%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aQ" x1="-684.479137%" x2="737.933813%" y1="-1012.53646%" y2="1046.99896%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + <linearGradient id="aR" x1="-802.736152%" x2="689.739334%" y1="-1056.80385%" y2="890.777014%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aS" x1="-1124.88665%" x2="549.535228%" y1="-1423.71471%" y2="673.128094%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aT" x1="-465.885211%" x2="339.528169%" y1="-152.931663%" y2="157.298039%"> + <stop stop-color="#FFE900" offset="28.07%"/> + <stop stop-color="#FFCC07" offset="32.21%"/> + <stop stop-color="#FF8119" offset="41.22%"/> + <stop stop-color="#FF0B36" offset="54.35%"/> + <stop stop-color="#FF0039" offset="55.5%"/> + <stop stop-color="#ED00B5" offset="85.24%"/> + </linearGradient> + <linearGradient id="aU" x1="-632.473239%" x2="759.889437%" y1="-217.098158%" y2="319.212821%"> + <stop stop-color="#FFCCD7" offset="40.06%"/> + <stop stop-color="#EDBEE2" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd"> + <path d="M150.1 145.9v-.2.2zM152.6 147.1c0 .9.3 1.9.9 2.8-.6-.9-.9-1.9-.9-2.8zM149.7 154.2c0-.2-.1-.5-.3-.6.2.2.3.4.3.6 0 0-.1.7.8 1.8-.9-1-.8-1.8-.8-1.8zM229.2 188.9c.4-1.5.7-3 .8-4.4 0-.5.1-1 .1-1.5 0 .5-.1 1-.1 1.5-.1 1.4-.4 2.9-.8 4.4zM103.1 216.7h.8l-.3-.3c-.1.2-.3.3-.5.3zM235.1 153.6v.2c.4.1.8.3 1.1.6.8.7 1 1.8.7 2.7.1-.2.1-.4.1-.6.1-1.3-.7-2.5-2-2.9v-.2l-.1-.9c-1.5-.1-3-.2-4.6-.4l-.3 3.3 5.1-1.8zM245.1 143.8c6.7-3.5 11.1-12.3 10.9-20.8.3 8.5-4.2 17.3-10.9 20.8-3.5 1.8-8.8 2.6- [...] + <path d="M239.9 150.3c-.8.1-1.6.2-2.4.2-.1-.1-.3-.1-.4-.1-2.1-.1-4.3-.2-6.4-.4v.1c2.1.2 4.2.4 6.3.5.1 0 .3 0 .4.1.8 0 1.6-.1 2.4-.2 6.9-.9 11-3.2 15.3-8.7 1.4-1.7 3-4.6 4.1-7.8-1.2 3.2-2.7 5.9-4.1 7.7-4.3 5.4-8.4 7.7-15.2 8.6zM104 200.6c0-.1 0-.1 0 0 0-.1 0-.1 0 0zM145.8 157.9l-.2-.3v-.1l.1.1M140.7 165.2h-.1l-.6.9v.1M252 110.6c-2.8-4.7-6.4-9.1-8.6-11.7 2.2 2.6 5.8 7 8.6 11.7zM206.9 117.5c-.2-.3-.5-.5-.7-.7-.6-.5-1.4-.7-2.1-.7 1 0 2.1.5 2.8 1.4.5.8 1.5 1 2.3.5.1-.1.2-.1.3-.2-.1.1-.2.1 [...] + <path d="M214.8 223.5v-.9c-1.3.2-2.6.4-3.8.6-4.1.6-8.2 1-12.4 1-6.3 0-12.6-.5-18.8-1.7 0 1.3 0 2.3-.1 3.3 4.7-.1 9.6-.2 14.7-.2 7.1 0 13.9.1 20.4.3v-2.4zM159.1 216.9c-.7-1.9-1.3-4.2-2-6.8h-1.3c-3.9-.1-7.9-.4-11.7-1.1v1.9h1c1 0 1.9 1 1.9 2.2v1.4c0 1.2-.8 2.1-1.7 2.2h.2l.4.3c.2-.2.4-.3.7-.3h.8c1.9.1 3.1.4 3.6.9 1.6 1.3 2.6 4.2 2.6 7.5 0 .5-.1 1.2-.2 1.9 3.1-.2 6.3-.4 9.8-.6-.6-1-1.1-2.1-1.6-3.2-.9-1.9-1.8-4.1-2.5-6.3zM235.4 114.9c-.2.1-.3.3-.3.4.1-.1.2-.2.3-.4.1 0 .1 0 0 0zM150.3 134.7 [...] + <path d="M152.3 147.3c-.2-3.1-.3-6.4-.4-9.8.2-1 .4-1.9.5-2.8 0-10.7.9-20.1 2.9-26v-.1c-2 5.9-2.9 15.3-2.9 26.1-.2.9-.3 1.8-.5 2.8v.2c0-.1 0-.2.1-.3 0 3.5.1 6.8.3 9.9 0 .1 0 .1 0 0zM236.4 182.5c-.4 15.2-3.8 25.4-5.2 29-.3 1.4-.7 2.7-1.1 3.8-1.6 4.5-3.5 8.5-5.2 11.1 1.7-2.6 3.7-6.6 5.3-11.2.4-1.1.7-2.4 1.1-3.8 1.4-3.6 4.8-13.7 5.2-29v-2.3s-.1 0-.1-.1v2.5zM149 153.6c.1-.6.3-1.3.6-1.9-.3.6-.6 1.2-.6 1.9h.2c-.1-.1-.2-.1-.2 0zM174.2 215.2v.2h-.1l.1-.2-.1.3c.1 2.1.1 4.1.1 6 0 1.7 0 3.2-.1 4 [...] + <path fill="#FFF" fill-rule="nonzero" d="M7.7 72.3v98.1c0 1 .1 1.2.1 1.2s.2.1 1.2.1h121.1l-.6-1.9c-.9-3.1.3-6.3 2.9-7.9V72.3H7.7zm45.8 65.5c0 1.8-1.5 3.3-3.3 3.3-1.8 0-3.3-1.5-3.3-3.3V98.4c0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3v39.4zm9.8 0c0 1.8-1.5 3.3-3.3 3.3-1.8 0-3.3-1.5-3.3-3.3V105c0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3v32.8zm9.9 0c0 1.8-1.5 3.3-3.3 3.3-1.8 0-3.3-1.5-3.3-3.3v-36.1c0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3v36.1zm20.8 3.1c-.4.1-.8.2-1.1.2-1.3 0-2.6-.8- [...] + <path fill="#FFF" fill-rule="nonzero" d="M127.3 173.2l1.8.1c.1-.2.3-.4.5-.5H9c-2 0-2.5-.4-2.5-2.5V71.2h127v90.2c.2-.1.4-.2.7-.2l3.6-1.1v-4.8c-1.3-2.3-1.2-5 0-7.1V55.4c0-2.3-1.9-4.2-4.2-4.2H6.5c-2.3 0-4.2 1.9-4.2 4.2v118.3c0 2 1.8 3.7 3.9 3.7h90c.3-.6.6-1.2 1.1-1.5.9-.7 2.6-.8 3.8-.8.5 0 .9.2 1.2.5l.5-.4h19.1c1.3-1.2 3-2 4.9-2h.5zm9.1-13.5l-.1-.2.1.2zm-.1-.3v.4l-.3-.9.3.5zm-9.6-101.2c1.6 0 2.9 1.3 2.9 2.9 0 1.6-1.3 2.9-2.9 2.9-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.9 2.9-2.9zm-9.2 0c1.6 0 [...] + <path fill="#D7D7DB" fill-rule="nonzero" d="M138.7 159.7c.3-.5.6-1.1.8-1.7l-1.7-2.8v4.7l.9-.2zM6.2 177.5c-2.2 0-3.9-1.6-3.9-3.7V55.4c0-2.3 1.9-4.2 4.2-4.2h127.1c2.3 0 4.2 1.9 4.2 4.2V148c.5-.9 1.3-1.7 2.2-2.3V55.4c0-3.6-2.9-6.5-6.5-6.5H6.5c-3.6 0-6.5 2.9-6.5 6.5v118.3c0 3.3 2.8 5.9 6.2 5.9h89.2c.2-.8.4-1.5.7-2.2H6.2v.1zM139 167.8l1.1-1.6v-.1l-1.1 1.7c-.2.1-.2.4 0 .5-.1-.1-.1-.3 0-.5z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M6.5 71.2v99.2c0 2 .4 2.5 2.5 2.5h120.5c.2-.2.4-.5.7-.7l-.1-.4H9c-1 0-1.2-.1-1.2-.1s-.1-.2-.1-1.2V72.3h124.7V162c.3-.2.7-.4 1.1-.6V71.2H6.5zM132.8 169.2c-.2-.7-.2-1.4 0-2.1-.3.6-.3 1.4 0 2.1l.7 2.3v-.1l-.7-2.2zM13.3 64c1.6 0 2.9-1.3 2.9-2.9 0-1.6-1.3-2.9-2.9-2.9-1.6 0-2.9 1.3-2.9 2.9 0 1.6 1.4 2.9 2.9 2.9zM22.6 64c1.6 0 2.9-1.3 2.9-2.9 0-1.6-1.3-2.9-2.9-2.9-1.6 0-2.9 1.3-2.9 2.9 0 1.6 1.3 2.9 2.9 2.9zM38.1 64.3H102c1.7 0 3.1-1.4 3.1-3.1V61c [...] + <path fill="#D7D7DB" fill-rule="nonzero" d="M60 101.6c-1.8 0-3.3 1.5-3.3 3.3v32.8c0 1.8 1.5 3.3 3.3 3.3 1.8 0 3.3-1.5 3.3-3.3v-32.8c0-1.8-1.4-3.3-3.3-3.3zM69.9 98.4c-1.8 0-3.3 1.5-3.3 3.3v36.1c0 1.8 1.5 3.3 3.3 3.3 1.8 0 3.3-1.5 3.3-3.3v-36.1c0-1.9-1.5-3.3-3.3-3.3zM50.2 95.1c-1.8 0-3.3 1.5-3.3 3.3v39.4c0 1.8 1.5 3.3 3.3 3.3 1.8 0 3.3-1.5 3.3-3.3V98.4c0-1.8-1.5-3.3-3.3-3.3zM82.8 100.5c-.6-1.7-2.5-2.6-4.2-2-1.7.6-2.6 2.5-2 4.2l13.1 36.1c.5 1.3 1.7 2.2 3.1 2.2.4 0 .8-.1 1.1-.2 1.7-.6 2. [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M40.9 25.6h97.9c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1H116c-2-3.7-7.1-11.7-13.4-12.9-8.4-1.6-10 6.7-10 6.7S87 2.7 73 4.6c-6.5.9-9 4.2-9.8 7.8h.1c.3 0 .6.2.6.5 0 .4.1.7.1 1.1 0 .3-.2.6-.5.6h-.1c-.2 0-.4-.1-.5-.3-.1 1.9.1 3.8.5 5.3h.7c-.1-.3-.2-.6-.4-1-.1-.3.1-.6.4-.7.3-.1.6.1.7.4.3 1 .6 1.7.6 1.7.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-1.3c.4 1.5.9 2.5.9 2.7H40.7c-.6 0-1.1.5-1.1 1.1.1.5.6 1 1.3 1z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M229.2 52.9c.3-1 1.2-3.2 3.8-3.2.3 0 .7 0 1 .1 1.6.3 3.2 1.3 4.8 3 .2.2.6.2.8 0 .2-.2.2-.6 0-.8-1.8-1.9-3.6-3-5.4-3.4-.4-.1-.8-.1-1.2-.1-3.5 0-4.6 3-4.9 4-.1.3.1.6.4.7h.2c.1 0 .2 0 .3-.1.1 0 .2-.1.2-.2zM213.5 48.4c.1 0 .3-.1.4-.2.6-.7 1.5-1.1 2.6-1.4.3-.1.5-.4.4-.7-.1-.3-.4-.5-.7-.4-1.3.4-2.4.9-3.1 1.7-.2.2-.2.6 0 .8.1.2.3.2.4.2zM246.3 56.9h3.3c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-3.3c-.3 0-.6.2-.6.6 0 .3.3.6.6.6zM220.7 46.6c.4.1.7.2 1 .3h.2c. [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M199.6 60.7h54.5c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-12.9c-1.1-2.1-3.9-6.5-7.4-7.2-2.4-.5-3.8.5-4.6 1.6 0 .1-.1.2-.2.3-.6 1-.8 1.9-.8 1.9s-3.1-8-10.9-7c-5.8.8-6 5-5.4 7.8h.7s.1-.1.2-.1c.3-.1.6 0 .7.3l.1.1c.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-.9c.2.9.5 1.4.5 1.5h-13.2.2c-.6 0-1.1.5-1.1 1.1-.1.6.4 1.1 1 1.1z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M173.7 229.1c-.1.2-.1.4-.2.5 0 0-.1 0-.1.1.1 0 .2-.1.3-.1.3-.3.5-1.6.6-3.6h-.1c-.2 1.5-.3 2.6-.5 3.1zM173.1 232c.5 0 1-.1 1.5-.4-.4.2-.9.4-1.5.4-2.1 0-4.3-2.7-6.1-5.8h-.1c1.8 3.2 4 5.8 6.2 5.8z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M231.1 226.7c-2.6 4.8-5.9 8.5-9.7 8.5-1.4 0-2.9-.5-4-1.4-1.7-1.4-2.5-2.9-2.6-7.8-6.4-.2-13.3-.3-20.4-.3-5 0-9.9.1-14.7.2-.2 5.1-1 6.6-2.7 7.9-1.1.9-2.5 1.4-4 1.4-4 0-6.8-3.6-8.7-6.8-.4-.6-.8-1.3-1.1-2-3.4.2-6.7.4-9.8.6-.3 2-1.1 4.5-2.2 5.4-1.1.9-3.1 1.1-4.5 1.1-.6 0-1-.3-1.4-.6l-.6.6H97.4c-1 0-1.9-.7-2.2-1.7l-.3-1.1v-.2c-5.9.7-9.4 1.5-9.4 2.4 0 2.1 17.6 3.7 39.4 3.7 3.5 0 6.8 0 10-.1 12.2 2.1 34.3 3.4 59.5 3.4 38.5 0 69.7-3.2 69.7-7.1 0-2.6 [...] + <path fill="#EDEDF0" fill-rule="nonzero" d="M219.5 231.3c.5.4 1.2.7 1.9.7.6 0 1.3-.2 1.9-.6-.6.4-1.2.6-1.8.6-.7 0-1.4-.3-2-.7-.6-.6-1.2-1.1-1.3-5.3h-.1c.2 4.3.8 4.8 1.4 5.3zM223.3 228.4c.5-.5 1-1.2 1.5-2-.5.8-1 1.4-1.5 2z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M102.1 188.2l-.3-.2c-.1.2-.3.3-.6.3h-.7c-1.6-.1-2.6-.3-3.1-.7l-.6-.6H83c-.6 0-1.1.5-1.1 1.1 0 .6.5 1.1 1.1 1.1h19.4c.1-.4.2-.7.5-.9h-.8v-.1zM156.8 189.1h.1c-.2-.8-.3-1.5-.4-2.2h-.1c.1.7.3 1.4.4 2.2zM27.3 194.1c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6H24c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h3.3zM19.5 193h-1.1c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h1.1c.3 0 .6-.2.6-.6 0-.3-.3-.6-.6-.6zM68.5 194.1c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-3.3c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h3.3zM [...] + <path fill="#EDEDF0" fill-rule="nonzero" d="M65.7 230.4c-.3.9-.6 1.7-1.1 2.1-.8.8-2.3.9-3.4.9-.4 0-.8-.3-1-.6l-.5.5H24.5h-.1c2.2 1.6 12.1 2.7 24 2.7 13.5 0 24.4-1.5 24.4-3.4 0-.7-2.7-1.6-7.1-2.2z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M28.3 224.9h32.9c.1 0 .1 0 .2.1-.1-.4-.1-.7-.3-1H27.2v4.6h33.7c.2-.3.3-.7.4-1.1h-33c-.2 0-.4-.2-.4-.4s.2-.4.4-.4h32.9c.1 0 .2 0 .2.1v-1.1c-.1.1-.2.1-.3.1H28.3c-.2 0-.4-.2-.4-.4s.2-.5.4-.5z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M59.2 231.8l.8-.9.3.1c.3.1.5.3.6.5.1.1.2.3.3.3 1.2 0 2.1-.2 2.5-.6.6-.5 1.3-3.3 1.3-5.1 0-2.6-.7-4.6-1.4-5.2-.1-.1-.8-.3-1.9-.4-.1.4-.2.7-.7.8h-.3l-.9-.9H24.3c-.1 0-.3.1-.3.3v1.2c0 .2.1.3.3.3H62l.2.4c1.3 2.4.8 5.9-.4 7.3l-.2.2H24.3c-.1 0-.2 0-.2.1s-.1.2 0 .3l.2 1c0 .1.1.2.2.2h34.7v.1z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M59.7 233.4l.5-.5c.2.3.6.6 1 .6 1.1 0 2.6-.2 3.4-.9.4-.4.8-1.2 1.1-2.1.5-1.5.7-3.3.7-4.3 0-2.8-.8-5.4-1.9-6.5-.4-.4-1.3-.7-2.7-.8h-.6c-.3 0-.4.1-.5.3l-.3-.3h-36c-.9 0-1.7.9-1.7 2v1.2c0 1.1.7 2 1.7 2h.9v4.6h-.9c-.5 0-1 .3-1.3.8-.3.5-.4 1.1-.3 1.7l.2 1c.2.8.8 1.4 1.5 1.5h35.2v-.3zm-35.5-1.8l-.2-1v-.3c0-.1.1-.1.2-.1h37.2l.2-.2c1.3-1.4 1.7-4.9.4-7.3l-.2-.4H24.3c-.1 0-.3-.1-.3-.3v-1.2c0-.2.1-.3.3-.3h35.5l.9.9h.3c.4-.1.6-.5.7-.8 1.2.1 1.8.3 1.9.4 [...] + <path fill="#FFF" fill-rule="nonzero" d="M174.2 215.4v-.2M236.7 157.9c.2-.2.3-.4.3-.6.1-.2.1-.5.1-.7 0 .2-.1.4-.1.6-.1.2-.2.4-.3.7zM161.3 204.2v.1h.1v-.1h-.1zM173.7 229.1c-.1.1-.1.2-.2.3-.4.3-1 .1-1.7-.5-.1 0-.1-.1-.2-.2-.3-.2-.5-.5-.8-.9.9 1.1 1.7 1.8 2.3 1.8h.2s.1 0 .1-.1c.1 0 .2-.1.3-.4z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M233.4 212.2c-.3 1.4-.7 2.7-1.1 3.8-.5 1.4-4.6 12.7-9 15.4-.6.4-1.3.6-1.9.6-.7 0-1.3-.2-1.9-.7-.7-.5-1.3-1-1.3-5.3 0-1.7 0-4.1.2-7.4-6.5 1.5-13.1 2.3-19.7 2.4-7.4.1-14.9-.7-22.1-2.6.2 11.4-.6 12-1.5 12.8-.1.1-.3.2-.5.3-.5.3-1 .4-1.5.4-2.2 0-4.4-2.6-6.2-5.8-2.5-4.2-4.3-9.3-4.6-10.2-1-3-1.9-6.1-2.6-9.2-1.3.1-2.6.1-3.9.1-3.9-.1-7.9-.5-11.7-1.1v3.2c3.9.7 7.8 1 11.7 1.1h1.3c.7 2.7 1.3 5 2 6.8.8 2.2 1.6 4.3 2.6 6.3.5 1.1 1 2.1 1.6 3.2.4.7.7 1.3 1 [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M137.8 148.1c-1.2 2.1-1.3 4.8 0 7.1v.1l1.7 2.8c-.3.6-.6 1.1-.8 1.7l-.8.3-3.6 1.1c-.2.1-.5.2-.7.2-.4.2-.8.4-1.1.6-2.5 1.7-3.8 4.9-2.9 7.9l.6 1.9.1.4c-.2.3-.4.5-.7.7-.2.2-.3.4-.5.5l-1.8-.1h-.6c-1.9 0-3.6.8-4.9 2h10.3l1.8-2.1-.5-1.7-.7-2.2c-.2-.7-.2-1.5 0-2.2.3-1.1 1.2-2.1 2.4-2.5l5.8-1.8c.8-1.4 1.6-3 2.3-4.7l-2.6-4.3c-.5-.9-.7-1.9-.4-2.8.2-.9.8-1.8 1.7-2.3l1.1-.7 4.8-2.9c.9-3.3 1.7-6.9 2.2-10.6 0-12.6 1.1-21.9 3.3-27.6l-.2-.5c-.4-1.2.3-2.4 1. [...] + <path fill="#FFF" fill-rule="nonzero" d="M136.3 159.7v-.3l-.2-.5.2.9M155.9 106.8l-.3-.9.3 1"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M230.3 174.3c0-1-.1-2-.2-2.9-.1-.7-.2-1.4-.2-2.1-.3-.1-.6-.1-1-.2l-.4 4.5c.6.2 1.2.4 1.8.7zM242.5 116.3c-.7-.6-1.6-.9-2.6-1-.8 0-1.6.2-2.3.7.7.6 1.7.9 2.6.9.9 0 1.7-.2 2.3-.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M174.5 205.9c9.6 4.4 20.4 5.7 30.8 3.8 14.7-2.9 21.6-12.6 23.9-20.8.4-1.5.7-3 .8-4.4 0-.5.1-1 .1-1.5.2-2.5.2-5 .2-7.5v-.1c-.6-.3-1.3-.6-1.9-.8l-1.1 11.7c-.1.9-.8 1.5-1.7 1.5l-22.1 3.1c-.9.9-2.9 2.3-6.5 2.5h-.8c-3.3 0-5.3-1.4-6.2-2.3l-24.9-3.6c-.9 0-1.6-.7-1.6-1.5l-1.2-15.4c-2.4.6-4.8 1.4-7.1 2.3.3 2 .6 4.2 1 6.4.1.1.2.2.2.4l.2.9c.6 3.2 2.6 14.1 4.8 23.4v.1h.1v.1h.1c.8 3.7 1.7 7.3 2.9 10.9 2 5.6 4.5 10.3 6.4 12.7.3.3.6.6.8.9.1 0 .1.1.2.2.7.6 1. [...] + <path fill="#FAFAFA" fill-rule="nonzero" d="M152 137.6c.1 3.3.2 6.6.4 9.8l.3-.3v.1c-.1.9.3 1.9.8 2.9.8 1.4 2.1 2.7 3.2 3.7 1.8-1 3.3-1.2 4-1.2l-.3-4.3c0-.5.1-1 .5-1.3.3-.3.8-.5 1.3-.5h.3l1.4-6.1c.1-.4.4-.8.8-.8.4-.1 10.5-2.3 19.1 1.5 7.1 3.1 11.3 7.9 13.1 10.5 1.5-2.6 5.2-7.4 12.7-11 6.3-3.1 15.5-3.4 16.7-2.9.3.1.6.4.7.7.2.6 1.3 4.5 1.9 7.1h.2c.5 0 1 .1 1.3.5.2.2.4.5.4.8 5.4-.1 10.7-.9 14.2-2.7 6.7-3.5 11.1-12.3 10.9-20.7 0-1.2-.2-2.4-.4-3.6-.6-2.9-2-5.9-3.7-8.9-2.8-4.7-6.4-9.1-8.6-1 [...] + <path fill="#FFF" fill-rule="nonzero" d="M161.7 166.1c-3.6.4-6.2-.6-7.8-1.9.3 2.1.7 4.7 1.1 7.6 2.3-.9 4.7-1.7 7.2-2.3l-.1-.8c-.2-.9-.4-1.7-.4-2.6z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M136.4 159.7l-.1-.3"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M156.4 186.9H145c-.1.3-.2.5-.4.7h.8l.3.2c.1-.2.3-.3.6-.3h.7c1.6.1 2.6.3 3.1.7.3.2.5.5.8.8h6c-.2-.7-.4-1.4-.5-2.1zM150.2 182.9c0-.3-.2-.6-.6-.6h-7.5v1.1h7.5c.3 0 .6-.2.6-.5z"/> + <path fill="url(#a)" fill-rule="nonzero" d="M252 110.6c1.7 3 3.1 6 3.7 8.9.2 1.2.4 2.4.4 3.6.2 8.5-4.2 17.3-10.9 20.8-3.5 1.9-8.8 2.6-14.2 2.7v.5l-.3 2.9c2.1.2 4.2.4 6.4.4.1 0 .3 0 .4.1.8 0 1.6-.1 2.4-.2 6.9-.9 11-3.2 15.3-8.7 1.4-1.7 2.9-4.5 4.1-7.7 1.5-4.1 2.4-8.8 1.3-12.8-1.2-4.7-4.7-9.3-7.7-12.5-2.9-3.8-6-7.3-9.5-10.6-.1-.1-.3-.2-.5-.2-.1 0-.3.1-.4.1.2.3.5.6.8.9 2.3 2.7 5.9 7.1 8.7 11.8z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M242.6 98c.2.3.5.6.8.9-.3-.3-.6-.6-.8-.9z"/> + <path fill="url(#b)" fill-rule="nonzero" d="M242.6 98c.2.3.5.6.8.9-.3-.3-.6-.6-.8-.9z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M252 110.6c1.7 3 3.1 6 3.7 8.9.2 1.2.4 2.4.4 3.6 0-1.2-.2-2.4-.4-3.6-.6-2.9-2-6-3.7-8.9z"/> + <path fill="url(#c)" fill-rule="nonzero" d="M252 110.6c1.7 3 3.1 6 3.7 8.9.2 1.2.4 2.4.4 3.6 0-1.2-.2-2.4-.4-3.6-.6-2.9-2-6-3.7-8.9z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M229.9 169.3c.1.7.1 1.4.2 2.1 0-.8-.1-1.5-.2-2.1z"/> + <path fill="url(#d)" fill-rule="nonzero" d="M229.9 169.3c.1.7.1 1.4.2 2.1 0-.8-.1-1.5-.2-2.1z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M220.4 226.2c0 1.8.2 3.1.5 3.3.1.1.3.2.5.2.5 0 1.2-.5 1.9-1.3.5-.5 1-1.2 1.5-2-1.4-.1-2.9-.2-4.4-.2z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M205.3 209.7c-10.4 1.9-21.2.6-30.8-3.8 9.6 4.4 20.3 5.8 30.8 3.8 14.7-2.8 21.6-12.5 23.9-20.8-2.3 8.3-9.2 17.9-23.9 20.8z"/> + <path fill="url(#e)" fill-rule="nonzero" d="M205.3 209.7c-10.4 1.9-21.2.6-30.8-3.8 9.6 4.4 20.3 5.8 30.8 3.8 14.7-2.8 21.6-12.5 23.9-20.8-2.3 8.3-9.2 17.9-23.9 20.8z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M230.1 183c.2-2.5.3-5 .2-7.4 0 2.4 0 4.9-.2 7.4z"/> + <path fill="url(#f)" fill-rule="nonzero" d="M230.1 183c.2-2.5.3-5 .2-7.4 0 2.4 0 4.9-.2 7.4z"/> + <path fill="url(#g)" fill-rule="nonzero" d="M236.4 180.1v-1c-1.9-1.3-3.9-2.4-5.9-3.4 0 .7.1 1.3.1 1.8 2 .8 3.9 1.4 5.8 2.6z"/> + <path fill="url(#h)" fill-rule="nonzero" d="M236.5 172.8c-.1-1.6-.1-3.1-.2-4.6-1.4 1-3.6 1.6-6.4 1.1.1.7.2 1.4.2 2.1.2 1.5.3 3 .4 4.3-.1 0-.1-.1-.2-.1 0 2.5 0 5-.2 7.4 0 .5-.1 1-.1 1.5-.1 1.4-.4 2.9-.8 4.4-2.3 8.3-9.2 18-23.9 20.8-10.4 1.9-21.2.6-30.8-3.8v2.9h.1c.3 0 .6.2.6.5l.3 6.4c2.9.9 10.8 3 23.3 3 7.3-.2 14.5-1.1 21.5-2.9 2.3-.9 4.4-2.2 6.3-3.8.2-.2.6-.2.8 0 .2.2.2.6 0 .8h-.1v.1c-1.9 1.7-4.1 3-6.4 4-.2 2.9-.3 5.7-.3 7.9v1.4c0 1.8.2 3.1.5 3.3.1.1.3.2.5.2.5 0 1.2-.5 1.9-1.3.5-.5 1 [...] + <path fill="url(#i)" fill-rule="nonzero" d="M202.7 108.7v3.5c0 .2 0 .4.1.6.4-.1.8-.1 1.2-.1.5 0 1.1.1 1.6.2.1-.2.2-.5.2-.7v-3.5c0-.9-.7-1.6-1.6-1.6-.8 0-1.5.7-1.5 1.6z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M202.8 112.8c-1.8.3-3.4 1.3-4.5 2.8-.4.7-.3 1.5.2 2.1.1.1.2.2.3.2.8.5 1.8.3 2.3-.5.4-.6 1-1 1.6-1.2h.1c.2-.1.4-.1.5-.1H203.9c.7 0 1.5.2 2.1.7.3.2.5.4.7.7.5.8 1.5 1 2.3.5.1-.1.2-.1.3-.2.6-.5.7-1.4.2-2.1-1-1.4-2.4-2.3-4-2.7-.5-.1-1.1-.2-1.6-.2-.3-.1-.7 0-1.1 0zm5.1 1.6c.1 0 .1.1.2.2s.3.2.4.4c.2.1.3.3.5.5.1.1.2.2.2.3l.3.3c.1.2.1.5.1.7v.1c0 .1-.1.2-.1.2 0 .1 0 .1-.1.2 0 .1-.1.1-.1.1l-.1.1h-.1-.1c-.1 0-.2.1-.2.1h-.1c-.4 0-.7-.1-1-.4-.8-1-2-1.7-3 [...] + <path fill="url(#j)" fill-rule="nonzero" d="M202.9 113.4c-.2 0-.4.1-.6.2-.2.1-.4.1-.5.2h-.1c-.2.1-.3.2-.5.2h-.1c-.2.1-.3.2-.5.3h-.1c-.3.2-.5.4-.8.7l-.1.1c-.2.2-.4.5-.6.7-.3.5-.2 1.2.3 1.5.4.3.9.2 1.2 0l.3-.3c.2-.2.4-.5.6-.7l.1-.1c.2-.1.4-.3.5-.4.1-.1.2-.1.4-.2.1-.1.3-.1.4-.2.2-.1.4-.1.6-.1H204c1.3 0 2.5.6 3.3 1.7.2.3.6.4 1 .4h.1c.1 0 .2 0 .2-.1.1 0 .1 0 .2-.1h.1l.1-.1.1-.1s.1-.1.1-.2.1-.1.1-.2v-.1c0-.2 0-.5-.1-.7l-.3-.3c-.1-.1-.1-.2-.2-.3-.1-.2-.3-.3-.5-.5-.1-.1-.3-.2-.4-.4-1.2-.8-3. [...] + <path fill="url(#k)" fill-rule="nonzero" d="M140 165.4v.7l.6-.9"/> + <path fill="#FFF" fill-rule="nonzero" d="M137.8 166.1l-1.9.6c-.8.2-1.2 1.1-1 1.8l1.1 3.7.2.6.1.2v.1l.1.4c-.5.6-1 1.1-1.4 1.7h2.4c.2-.4.3-.9.3-1.4v-7.7h.1z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M140 168.2l-.1.2c-.2.3-.5.3-.8.2l-.2-.2c-.1-.2-.1-.4 0-.6l1.1-1.6v-.7l-2.2.7v7.7c0 .5-.1 1-.3 1.4h2.3c.1-.5.2-.9.2-1.4v-5.7z"/> + <path fill="url(#l)" fill-rule="nonzero" d="M154.4 170.6c-3.5 1.5-6.8 3.4-9.9 5.6.1.1.1.2.2.4l.2.7c3.1-2 6.3-3.8 9.7-5.2-.1-.6-.1-1.1-.2-1.5z"/> + <path fill="url(#m)" fill-rule="nonzero" d="M156.8 189.1c-.2-.8-.3-1.5-.4-2.2-.8-4.1-1.3-6.9-1.3-6.9 0-.3.1-.5.4-.6.1-.1.2-.1.2-.1.2 0 .3 0 .4.1-.4-2.2-1.1-5.2-1.5-7.4.1-.1.2-.1.4-.2-.4-2.9-.8-5.5-1.1-7.7-1.5-1.3-2.1-2.8-2-3.7v-.1c-1.2-.6-2.1-1.4-2.8-2.1-1.4-1.5-1.7-3-1.7-3.2v-.4c.1-.4.5-.8.9-.8h.3l.2-.2c.1-.6.3-1.3.6-1.9.8-1.9 2.1-3.5 2.7-4.3-.1-3.2-.2-6.5-.3-9.9 0 .1 0 .2-.1.3l-.6 3c-.1.2-.1.5-.2.7-.3 1.1-.5 2.3-.9 3.5-.1.2-.1.4-.2.6v.2l-.1.2-.1.5h-.1l-1.5 4.1c-.1.2-.3.4-.6.3h-.1-. [...] + <path fill="#F9F9FA" fill-rule="nonzero" d="M149.2 153.6s0-.1 0 0c.2 0 .2 0 .3.1.2.1.3.3.3.6 0 0-.1.7.8 1.8.5.6 1.3 1.2 2.5 1.9.2-.9.7-1.7 1.5-2.5s1.5-1.3 2.3-1.7c-1.2-1.1-2.4-2.4-3.2-3.7-.6-.9-.9-1.9-.9-2.8v-.1l-.3.3c-.6.7-1.9 2.4-2.7 4.3-.3.6-.5 1.3-.6 1.9-.2-.2-.1-.2 0-.1z"/> + <path fill="url(#n)" fill-rule="nonzero" d="M136.3 173.1l-.3-.9-1.1-3.7c-.2-.8.2-1.6 1-1.8l1.9-.6 2.2-.7.6-.2h.1l-.7 1-1.1 1.6c-.1.2-.1.4 0 .5 0 .1.1.2.2.2.3.2.6.1.8-.2l.1-.2 2.4-3.5h.1c1.2-2.2 2.4-4.4 3.3-6.7v-.1l-.2-.3-.1-.2-.1-.1-2.9-4.7c-.4-.7-.2-1.6.5-2l4.7-2.9.3-.2.1-.1-1 2.8c-.1.3.1.6.3.7h.1c.2 0 .5-.1.6-.3l1.5-4.1h.1l.1-.5.1-.2v-.2c.1-.2.1-.4.2-.6.3-1.2.6-2.3.9-3.5.1-.2.1-.5.2-.7l.6-3v-.2c.2-.9.3-1.9.5-2.8 0-10.8.9-20.2 2.9-26.1v.1l.7 1.7c.1.2.3.3.5.4h.2c.3-.1.4-.4.3-.7l-1.2- [...] + <path fill="url(#o)" fill-rule="nonzero" d="M237.3 167.2c-.2.3-.6.7-1 1 0 .9.1 1.7.2 2.4.1.5 0 1.3 0 2.2 0 2-.2 4.6 0 6.1v3.5c-.4 15.3-3.8 25.4-5.2 29-.4 1.4-.7 2.6-1.1 3.8-1.6 4.6-3.6 8.6-5.3 11.2-.5.8-1.1 1.5-1.5 2-.7.8-1.4 1.3-1.9 1.3-.2 0-.3-.1-.5-.2-.3-.3-.5-1.5-.5-3.3v-1-.3-.1c0-2.2.2-5.1.3-7.9v-.1c2.4-.9 4.6-2.3 6.5-4h.1c.2-.2.2-.6 0-.8-.2-.2-.6-.2-.8 0-1.9 1.6-4 2.9-6.3 3.8-7.1 1.8-14.3 2.7-21.5 2.9-12.5 0-20.4-2.1-23.3-3l-.3-6.4c0-.3-.3-.5-.6-.5h-.1c-.1 0-.2.1-.3.2-.1.1-.1.2 [...] + <path fill="url(#p)" fill-rule="nonzero" d="M152 160.4c.2-.2 1.1.2 1-.1-.2-.8-.2-1.6 0-2.4-1.2-.7-2-1.3-2.5-1.9-.9-1-.8-1.8-.8-1.8 0-.2-.1-.4-.2-.6-.1 0-.1-.1-.2-.1H149c-.1 0-.2.1-.2.2h-.3c-.5.1-.8.4-.9.8v.4c0 .2.3 1.7 1.7 3.2.6 1 1.5 1.7 2.7 2.3z"/> + <path fill="url(#q)" fill-rule="nonzero" d="M161.9 166s-.1 0-.2.1c.1.9.2 1.8.4 2.6l-.2-2.7z"/> + <path fill="url(#r)" fill-rule="nonzero" d="M241.3 112.7c.1-.2.2-.5.2-.7v-3.5c0-.5-.3-1-.7-1.3-.3-.2-.6-.3-.9-.3-.9 0-1.6.7-1.6 1.6v3.5c0 .2 0 .4.1.6h.3c.3 0 .6-.1.9-.1.6 0 1.2.1 1.7.2z"/> + <path fill="url(#s)" fill-rule="nonzero" d="M235.1 117.3c.3.2.7.2 1 .1.2-.1.4-.2.6-.4.3-.4.7-.7 1.1-1 .7-.4 1.4-.7 2.3-.7 1 0 1.9.4 2.6 1 .3.2.5.4.7.7.4.5 1.1.6 1.6.2.4-.3.6-.9.3-1.4-.2-.4-.5-.7-.8-1-.8-.8-1.8-1.3-2.8-1.5-.8-.2-1.6-.2-2.4-.1-1 .1-1.9.5-2.7 1.1-.3.2-.6.4-.8.7l-.4.4-.4.4c-.6.5-.5 1.2.1 1.5z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M102.5 224.6c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h44.2c.1 0 .2 0 .2.1-.1-.4-.2-.8-.4-1.2H101v5.2h45.2c.2-.3.4-.8.6-1.3H102.4c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h44.2c.1 0 .2 0 .3.1.1-.4.1-.8 0-1.3-.1.1-.2.2-.3.2h-44.1v.2z"/> + <path fill="url(#t)" fill-rule="nonzero" d="M96.8 230.3c.9-.1 1.9-.2 2.9-.3v-.3h-2.6c-.1 0-.2 0-.3.1 0 .2-.1.3 0 .5z"/> + <path fill="url(#u)" fill-rule="nonzero" d="M151.8 225.1c0-2.9-1-5.2-1.9-6-.2-.1-1-.4-2.6-.5-.1.4-.3.9-.9.9l-.4.1-1.2-1H97.1c-.2 0-.3.2-.3.3v1.4c0 .2.2.3.3.3h15.6l34.2-.6.6.6h.1l.2.3 1.1 1.1-.2 1.1c.3 1.4.2 3-.2 4.2l3-.3c.2-.6.3-1.3.3-1.9z"/> + <path fill="#EDEDF0" fill-rule="nonzero" d="M142.6 230.1h-43v-.1c-1 .1-2 .2-2.9.3l.3 1c0 .2.2.3.3.3H144l1.1-1 .5.1c.4.1.6.3.8.6l.4.4c1.6 0 2.8-.3 3.3-.7.5-.4 1.1-2.1 1.5-3.8l-3 .3c-.2.5-.4 1-.6 1.4l-.2 1-5.2.2z"/> + <path fill="url(#v)" fill-rule="nonzero" d="M142.6 230.1h-43v-.1c-1 .1-2 .2-2.9.3l.3 1c0 .2.2.3.3.3H144l1.1-1 .5.1c.4.1.6.3.8.6l.4.4c1.6 0 2.8-.3 3.3-.7.5-.4 1.1-2.1 1.5-3.8l-3 .3c-.2.5-.4 1-.6 1.4l-.2 1-5.2.2z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M112.7 220.7h34.9l-.7-.6"/> + <path fill="url(#w)" fill-rule="nonzero" d="M112.7 220.7h34.9l-.7-.6"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M147.9 221l.1.1c.4.6.7 1.3.8 2l.2-1.1-1.1-1z"/> + <path fill="url(#x)" fill-rule="nonzero" d="M147.9 221l.1.1c.4.6.7 1.3.8 2l.2-1.1-1.1-1z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M99.7 230.1h43l5.1-.3.2-1c-.2.3-.4.5-.6.7l-.3.3H99.7v.3z"/> + <path fill="url(#y)" fill-rule="nonzero" d="M99.7 230.1h43l5.1-.3.2-1c-.2.3-.4.5-.6.7l-.3.3H99.7v.3z"/> + <path fill="url(#z)" fill-rule="nonzero" d="M95.2 231.8c.2 1 1.1 1.7 2.2 1.7h47.3l.6-.6c.3.3.8.6 1.4.6 1.5 0 3.4-.2 4.5-1.1 1.1-.9 1.9-3.4 2.2-5.4.1-.7.2-1.4.2-1.9 0-3.2-1-6.2-2.6-7.5-.6-.4-1.8-.7-3.6-.9h-.8c-.3 0-.6.1-.7.3l-.4-.3H97c-1.2 0-2.2 1-2.2 2.2v1.4c0 1.2 1 2.2 2.2 2.2h1.2v5.2H97c-.7 0-1.3.3-1.8.9-.4.5-.5 1.1-.4 1.7v.2l.4 1.3zm1.6-1.9c.1-.1.2-.1.3-.1h50l.3-.3c.2-.2.4-.4.6-.7.3-.4.5-.9.6-1.4.4-1.3.5-2.8.2-4.2-.2-.7-.4-1.4-.8-2l-.1-.1-.2-.3H97.2c-.2 0-.3-.2-.3-.3v-1.4c0-.2.2-. [...] + <path fill="url(#A)" fill-rule="nonzero" d="M147 223.8c-.1 0-.2-.1-.2-.1h-44.2c-.3 0-.5.2-.5.5s.2.5.5.5h44.2c.1 0 .3-.1.3-.2.1-.1.1-.2.1-.3 0-.2-.1-.3-.2-.4z"/> + <path fill="url(#B)" fill-rule="nonzero" d="M102.5 225.6c-.3 0-.5.2-.5.5s.2.5.5.5H146.9c.2-.1.3-.2.3-.4 0-.1-.1-.3-.2-.4-.1-.1-.2-.1-.3-.1h-44.2v-.1z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M103 210.8h38.8v-5.2h-38.5c-.8 1.1-1 3.5-.3 5.2z"/> + <path fill="url(#C)" fill-rule="nonzero" d="M134.5 203.4c-1.4-.5-2.8-.9-4.2-1.5h-.2l4.2 1.5h.2z"/> + <path fill="url(#D)" fill-rule="nonzero" d="M145.3 203.6c-2.5-.5-5.1-1-7.6-1.8h-.2c2.5.8 5.1 1.4 7.8 1.8z"/> + <path fill="url(#E)" fill-rule="nonzero" d="M103.2 213.9l.4-.1 1 1h40.6c.2 0 .3-.2.3-.3v-1.4c0-.2-.1-.3-.3-.3h-13.3l-29.1.6-.5-.6h-.1l-.2-.3-.9-1.1.1-1.1c-.4-2-.1-4.2.7-5.6l.1-1 4.4-.3h18.5c-.8-.4-1.7-.9-2.6-1.5h-17.1l-.9 1-.4-.1c-.3-.1-.5-.3-.7-.6-.1-.1-.2-.3-.3-.4-1.3 0-2.4.3-2.8.7-.7.6-1.4 3.8-1.4 5.9 0 2.9.8 5.2 1.6 6 .1.1.9.4 2.2.5 0-.5.2-.9.7-1z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M134.3 203.4c-1.4-.5-2.8-.9-4.2-1.5h-7.8c.9.6 1.8 1.1 2.6 1.5h9.4z"/> + <path fill="url(#F)" fill-rule="nonzero" d="M134.3 203.4c-1.4-.5-2.8-.9-4.2-1.5h-7.8c.9.6 1.8 1.1 2.6 1.5h9.4z"/> + <path fill="url(#G)" fill-rule="nonzero" d="M145.3 203.6c.1-.1.1-.2.1-.3l-.2-1.1c0-.2-.1-.3-.3-.3h-7.2c2.5.7 5 1.3 7.6 1.7z"/> + <path fill="url(#H)" fill-rule="nonzero" d="M145.3 203.6c.1-.1.1-.2.1-.3l-.2-1.1c0-.2-.1-.3-.3-.3h-7.2c2.5.7 5 1.3 7.6 1.7z"/> + <path fill="url(#I)" fill-rule="nonzero" d="M142.9 203.4v.3h2.2c.1 0 .1 0 .2-.1-2.6-.4-5.2-1-7.8-1.8h-7.2l4.2 1.5h8.4v.1z"/> + <path fill="url(#J)" fill-rule="nonzero" d="M142.9 203.4v.3h2.2c.1 0 .1 0 .2-.1-2.6-.4-5.2-1-7.8-1.8h-7.2l4.2 1.5h8.4v.1z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M131.8 212.7h-29.6l.5.7"/> + <path fill="url(#K)" fill-rule="nonzero" d="M131.8 212.7h-29.6l.5.7"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M101.9 212.4l-.1-.1c-.3-.6-.6-1.3-.7-2l-.1 1.1.9 1z"/> + <path fill="url(#L)" fill-rule="nonzero" d="M101.9 212.4l-.1-.1c-.3-.6-.6-1.3-.7-2l-.1 1.1.9 1z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M134.5 203.4h-28.1l-4.4.3-.1 1c.1-.3.3-.5.5-.7l.2-.3H143v-.3h-8.5z"/> + <path fill="url(#M)" fill-rule="nonzero" d="M134.5 203.4h-28.1l-4.4.3-.1 1c.1-.3.3-.5.5-.7l.2-.3H143v-.3h-8.5z"/> + <path fill="url(#N)" fill-rule="nonzero" d="M103 216.8h.2c.2 0 .4-.1.5-.3l.3.3H145.4c1-.1 1.7-1.1 1.7-2.2v-1.4c0-1.2-.9-2.2-1.9-2.2h-1v-5.5h1c.6 0 1.1-.3 1.5-.9.2-.3.3-.5.3-.8.1-.3.1-.7 0-1.1l-.2-1.1c-.1-.4-.3-.8-.5-1.1-.4-.1-.7-.3-1-.5l-.5.4h-40.1c-.2 0-.3 0-.5-.1-.4-.1-.7-.3-.9-.6h-.2c-1.2 0-2.9.2-3.9 1.1-1.3 1.3-2 5.5-2 7.4 0 3.2.9 6.2 2.2 7.5.5.4 1.5.7 3.1.9.1.1.3.2.5.2zm-2.8-2.5c-.8-.8-1.6-3.1-1.6-6 0-2.1.8-5.3 1.4-5.9.4-.4 1.5-.6 2.8-.7.1.1.3.3.3.4.2.3.4.5.7.6l.4.1.9-1H144.8c.1 [...] + <path fill="#FAFAFA" fill-rule="nonzero" d="M108.9 193.8c-.2 0-.4-.2-.4-.4s.2-.4.4-.4h37.5c.1 0 .1 0 .2.1l-.3-.9h-38.6v4.1H146c.2-.3.4-.6.5-1h-37.6c-.2 0-.4-.2-.4-.4s.2-.4.4-.4h37.5c.1 0 .2 0 .3.1 0-.3.1-.7 0-1-.1.1-.2.1-.3.1h-37.5v.1z"/> + <path fill="url(#O)" fill-rule="nonzero" d="M104.3 198.9c0 .1.1.2.3.2h13.9c-.4-.4-.9-.8-1.3-1.1h-10.7v-.3h-2.2c-.1 0-.2 0-.2.1-.1.1-.1.1-.1.2l.3.9z"/> + <path fill="url(#P)" fill-rule="nonzero" d="M104.2 189.1c-.1 0-.2.1-.2.2v1.1c0 .1.1.3.3.3h9.3c0-.6.1-1.1.2-1.6h-9.6z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M113.8 189.1h-9.5-.1 9.6z"/> + <path fill="url(#Q)" fill-rule="nonzero" d="M113.8 189.1h-9.5-.1 9.6z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M117.2 198c.4.4.8.8 1.3 1.1h5.7c-.7-.4-1.4-.7-2-1.1h-5z"/> + <path fill="url(#R)" fill-rule="nonzero" d="M117.2 198c.4.4.8.8 1.3 1.1h5.7c-.7-.4-1.4-.7-2-1.1h-5z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M113.8 189.1c-.1.5-.2 1.1-.2 1.6h3.4c.1-.6.2-1.1.4-1.6h-3.6z"/> + <path fill="url(#S)" fill-rule="nonzero" d="M113.8 189.1c-.1.5-.2 1.1-.2 1.6h3.4c.1-.6.2-1.1.4-1.6h-3.6z"/> + <path fill="url(#T)" fill-rule="nonzero" d="M142.9 198h-15.8c.9.4 1.7.8 2.6 1.1h14.4l.9-.8.4.1c.3.1.5.3.7.5.1.1.2.3.3.3 1.3 0 2.4-.2 2.8-.5.7-.5 1.4-2.9 1.4-4.6 0-2.3-.8-4-1.6-4.6-.1-.1-.8-.3-2-.4h-.2c-.1.3-.3.6-.7.7h-.3l-1-.7h-13.3c-.4.5-.7.9-1.1 1.4l16.2-.3.5.5h.1l.2.2.9.8-.1.9c.4 1.6.1 3.2-.7 4.3l-.1.8-4.5.3z"/> + <path fill="url(#T)" fill-rule="nonzero" d="M142.9 198h-15.8c.9.4 1.7.8 2.6 1.1h14.4l.9-.8.4.1c.3.1.5.3.7.5.1.1.2.3.3.3 1.3 0 2.4-.2 2.8-.5.7-.5 1.4-2.9 1.4-4.6 0-2.3-.8-4-1.6-4.6-.1-.1-.8-.3-2-.4h-.2c-.1.3-.3.6-.7.7h-.3l-1-.7h-13.3c-.4.5-.7.9-1.1 1.4l16.2-.3.5.5h.1l.2.2.9.8-.1.9c.4 1.6.1 3.2-.7 4.3l-.1.8-4.5.3z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M146.8 189.1h.2-.2z"/> + <path fill="url(#U)" fill-rule="nonzero" d="M146.8 189.1h.2-.2zM146.8 189.1h.2-.2z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M144.8 189.1h-13.3 13.3z"/> + <path fill="url(#V)" fill-rule="nonzero" d="M144.8 189.1h-13.3 13.3zM144.8 189.1h-13.3 13.3z"/> + <path fill="url(#W)" fill-rule="nonzero" d="M119.8 189.1c-.3.5-.5 1-.6 1.6l10.5-.2c.3-.5.7-.9 1-1.4h-10.9z"/> + <path fill="url(#W)" fill-rule="nonzero" d="M119.8 189.1c-.3.5-.5 1-.6 1.6l10.5-.2c.3-.5.7-.9 1-1.4h-10.9z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M130.8 189.1h-10.9 10.9z"/> + <path fill="url(#X)" fill-rule="nonzero" d="M130.8 189.1h-10.9 10.9zM130.8 189.1h-10.9 10.9z"/> + <path fill="url(#Y)" fill-rule="nonzero" d="M129.7 190.5h.6c.4-.5.7-.9 1.1-1.4h-.7c-.3.5-.6.9-1 1.4z"/> + <path fill="url(#Y)" fill-rule="nonzero" d="M129.7 190.5h.6c.4-.5.7-.9 1.1-1.4h-.7c-.3.5-.6.9-1 1.4z"/> + <path fill="url(#Y)" fill-rule="nonzero" d="M129.7 190.5h.6c.4-.5.7-.9 1.1-1.4h-.7c-.3.5-.6.9-1 1.4z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M131.5 189.1h-.7.7z"/> + <path fill="url(#Z)" fill-rule="nonzero" d="M131.5 189.1h-.7.7zM131.5 189.1h-.7.7zM131.5 189.1h-.7.7z"/> + <path fill="url(#aa)" fill-rule="nonzero" d="M122.2 198c.6.4 1.3.8 2 1.1h5.5c-.9-.4-1.8-.7-2.6-1.1h-4.9z"/> + <path fill="url(#ab)" fill-rule="nonzero" d="M122.2 198c.6.4 1.3.8 2 1.1h5.5c-.9-.4-1.8-.7-2.6-1.1h-4.9z"/> + <path fill="url(#ac)" fill-rule="nonzero" d="M119.8 189.1h-2.5c-.2.5-.3 1-.4 1.6h2.3c.1-.6.3-1.1.6-1.6z"/> + <path fill="url(#ad)" fill-rule="nonzero" d="M119.8 189.1h-2.5c-.2.5-.3 1-.4 1.6h2.3c.1-.6.3-1.1.6-1.6z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M117.2 198H142.9l4.4-.2.1-.8c-.1.2-.3.4-.5.5l-.2.2h-40.2v.3h10.7z"/> + <path fill="url(#ae)" fill-rule="nonzero" d="M117.2 198H142.9l4.4-.2.1-.8c-.1.2-.3.4-.5.5l-.2.2h-40.2v.3h10.7z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M130.3 190.5h-.6l-10.5.2h-1.6 29.5l-.5-.5"/> + <path fill="url(#af)" fill-rule="nonzero" d="M130.3 190.5h-.6l-10.5.2h-1.6 29.5l-.5-.5"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M147.4 191l.1.1c.3.5.6 1 .7 1.6l.1-.9-.9-.8z"/> + <path fill="url(#ag)" fill-rule="nonzero" d="M147.4 191l.1.1c.3.5.6 1 .7 1.6l.1-.9-.9-.8z"/> + <path fill="url(#ah)" fill-rule="nonzero" d="M104.1 200.5c.2 0 .3.1.5.1h40.1l.5-.4c.2.2.6.4 1 .5h.2c1.2 0 2.9-.2 3.8-.8 1.3-1 2-4.2 2-5.7 0-2-.6-3.8-1.4-5-.2-.3-.5-.6-.8-.8-.5-.3-1.5-.6-3.1-.7h-.7c-.3 0-.5.1-.6.3l-.3-.2h-.8c-.3.4-.8.6-1.4.6h-40.2c-.2.3-.4.6-.5.9v1.3c0 1 .8 1.7 1.9 1.7h1v4.1h-1c-.6 0-1.1.2-1.5.7-.4.4-.5 1-.3 1.5l.2.9c.1.3.2.5.4.7.2 0 .5.2 1 .3-.1 0-.1 0 0 0zm0-2.7c.1-.1.1-.1.2-.1H146.7l.2-.2.5-.5c.8-1.1 1.1-2.8.7-4.3-.1-.6-.4-1.1-.7-1.6l-.1-.1-.2-.2h-42.8c-.2 0-.3-.1- [...] + <path fill="url(#ai)" fill-rule="nonzero" d="M146.6 193.1c-.1 0-.1-.1-.2-.1h-37.5c-.2 0-.4.2-.4.4s.2.4.4.4h37.5c.1 0 .2 0 .3-.1.1-.1.1-.2.1-.3 0-.1-.1-.2-.2-.3z"/> + <path fill="url(#aj)" fill-rule="nonzero" d="M108.9 194.6c-.2 0-.4.2-.4.4s.2.4.4.4h37.6c.2 0 .3-.2.3-.3 0-.1-.1-.2-.1-.3-.1-.1-.2-.1-.3-.1h-37.5v-.1z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M101.1 183.6h38.6v-4.1h-38.4c-.6 1-.8 2.8-.2 4.1z"/> + <path fill="url(#ak)" fill-rule="nonzero" d="M98.4 186.4c.1.1.9.3 2.2.4.1-.3.3-.7.8-.7h.3l1 .8h11.9c.2-.5.5-1 .8-1.4l-14.6.2-.5-.5h-.1l-.2-.2-.9-.8.1-.9c-.3-1.2-.2-2.5.2-3.5H97c-.2.7-.3 1.4-.3 2 .1 2.2.9 4 1.7 4.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M120.7 176.7h-17.3l-.9.8h17.9c0-.3.1-.5.3-.8z"/> + <path fill="url(#al)" fill-rule="nonzero" d="M120.7 176.7h-17.3l-.9.8h17.9c0-.3.1-.5.3-.8z"/> + <path fill="#FFF" fill-rule="nonzero" d="M102 177.4c-.3-.1-.5-.3-.7-.5-.1-.1-.2-.3-.3-.3-1.3 0-2.4.2-2.8.5l-.3.3h4.5-.4z"/> + <path fill="url(#am)" fill-rule="nonzero" d="M102 177.4c-.3-.1-.5-.3-.7-.5-.1-.1-.2-.3-.3-.3-1.3 0-2.4.2-2.8.5l-.3.3h4.5-.4z"/> + <path fill="#FFF" fill-rule="nonzero" d="M133 177.5c.2-.3.5-.5.8-.8l-.8.8z"/> + <path fill="url(#an)" fill-rule="nonzero" d="M133 177.5c.2-.3.5-.5.8-.8l-.8.8z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M100 178.9l.1-.8 4.4-.2h15.6c0-.1.1-.2.2-.4H97.9c-.3.5-.7 1.3-.9 2.2h2.5c.2-.3.3-.6.5-.8z"/> + <path fill="url(#ao)" fill-rule="nonzero" d="M100 178.9l.1-.8 4.4-.2h15.6c0-.1.1-.2.2-.4H97.9c-.3.5-.7 1.3-.9 2.2h2.5c.2-.3.3-.6.5-.8z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M133 177.5c-.2.1-.3.2-.4.4l.4-.4z"/> + <path fill="url(#ap)" fill-rule="nonzero" d="M133 177.5c-.2.1-.3.2-.4.4l.4-.4z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M120.2 185.3l-4.7.1c-.3.4-.6.9-.8 1.4h4.1c.3-.6.8-1.1 1.4-1.5z"/> + <path fill="url(#aq)" fill-rule="nonzero" d="M120.2 185.3l-4.7.1c-.3.4-.6.9-.8 1.4h4.1c.3-.6.8-1.1 1.4-1.5z"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M120.1 177.9h4c.7-.7 1.6-1.1 2.6-1.1h.3l3.3.3c.1-.1.2-.2.3-.4h-10c-.1.3-.3.5-.4.8 0 .1 0 .2-.1.4z"/> + <path fill="url(#ar)" fill-rule="nonzero" d="M120.1 177.9h4c.7-.7 1.6-1.1 2.6-1.1h.3l3.3.3c.1-.1.2-.2.3-.4h-10c-.1.3-.3.5-.4.8 0 .1 0 .2-.1.4z"/> + <path fill="url(#as)" fill-rule="nonzero" d="M143.1 186.7c.2 0 .3-.1.3-.3v-1.1c0-.1-.1-.3-.3-.3h-7.9l-1.6 1.6h9.5v.1z"/> + <path fill="url(#at)" fill-rule="nonzero" d="M143.1 186.7c.2 0 .3-.1.3-.3v-1.1c0-.1-.1-.3-.3-.3h-7.9l-1.6 1.6h9.5v.1z"/> + <path fill="url(#au)" fill-rule="nonzero" d="M122 186.7h10.7c.3-.3.6-.6.8-.9l.7-.7h-4.4l-5.8.1c-.7.6-1.4 1.1-2 1.5z"/> + <path fill="url(#av)" fill-rule="nonzero" d="M122 186.7h10.7c.3-.3.6-.6.8-.9l.7-.7h-4.4l-5.8.1c-.7.6-1.4 1.1-2 1.5z"/> + <path fill="url(#aw)" fill-rule="nonzero" d="M140.9 177.9v.3h1c.4-.3.9-.7 1.3-1l-.1-.2c0-.1-.1-.2-.3-.2h-3.6c-.2.4-.5.8-.9 1.1h2.6z"/> + <path fill="url(#ax)" fill-rule="nonzero" d="M140.9 177.9v.3h1c.4-.3.9-.7 1.3-1l-.1-.2c0-.1-.1-.2-.3-.2h-3.6c-.2.4-.5.8-.9 1.1h2.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M133.9 177.5c.9 0 1.7-.3 2.4-.8h-2.5c-.2.3-.5.5-.8.8h.9z"/> + <path fill="url(#ay)" fill-rule="nonzero" d="M133.9 177.5c.9 0 1.7-.3 2.4-.8h-2.5c-.2.3-.5.5-.8.8h.9z"/> + <path fill="url(#az)" fill-rule="nonzero" d="M133.9 177.5c.9 0 1.7-.3 2.4-.8h-2.5c-.2.3-.5.5-.8.8h.9z"/> + <path fill="#D7D7DB" fill-rule="nonzero" d="M133 177.5l-.4.4h5.7c.3-.3.6-.7.9-1.1h-3c-.7.5-1.5.8-2.4.8h-.8v-.1z"/> + <path fill="url(#aA)" fill-rule="nonzero" d="M133 177.5l-.4.4h5.7c.3-.3.6-.7.9-1.1h-3c-.7.5-1.5.8-2.4.8h-.8v-.1z"/> + <path fill="url(#aB)" fill-rule="nonzero" d="M133 177.5l-.4.4h5.7c.3-.3.6-.7.9-1.1h-3c-.7.5-1.5.8-2.4.8h-.8v-.1z"/> + <path fill="url(#aC)" fill-rule="nonzero" d="M132.7 186.7h.9c.5-.6 1.1-1.1 1.6-1.6h-.9l-.7.7-.9.9z"/> + <path fill="url(#aC)" fill-rule="nonzero" d="M132.7 186.7h.9c.5-.6 1.1-1.1 1.6-1.6h-.9l-.7.7-.9.9z"/> + <path fill="url(#aD)" fill-rule="nonzero" d="M132.7 186.7h.9c.5-.6 1.1-1.1 1.6-1.6h-.9l-.7.7-.9.9z"/> + <path fill="url(#aE)" fill-rule="nonzero" d="M143.1 178.1c.1 0 .2 0 .2-.1.1-.1.1-.1.1-.2l-.2-.7c-.4.3-.9.7-1.3 1h1.2z"/> + <path fill="url(#aE)" fill-rule="nonzero" d="M143.1 178.1c.1 0 .2 0 .2-.1.1-.1.1-.1.1-.2l-.2-.7c-.4.3-.9.7-1.3 1h1.2z"/> + <path fill="url(#aF)" fill-rule="nonzero" d="M143.1 178.1c.1 0 .2 0 .2-.1.1-.1.1-.1.1-.2l-.2-.7c-.4.3-.9.7-1.3 1h1.2z"/> + <path fill="url(#aG)" fill-rule="nonzero" d="M127 176.8h-.3c-1 0-1.9.4-2.6 1.1h8.5l.4-.4c.2-.3.5-.5.8-.8h-3c-.1.1-.2.2-.3.4l-3.5-.3z"/> + <path fill="url(#aH)" fill-rule="nonzero" d="M127 176.8h-.3c-1 0-1.9.4-2.6 1.1h8.5l.4-.4c.2-.3.5-.5.8-.8h-3c-.1.1-.2.2-.3.4l-3.5-.3z"/> + <path fill="url(#aI)" fill-rule="nonzero" d="M120.2 185.3c-.6.5-1.1 1-1.5 1.5h3.4c.6-.5 1.3-1 2-1.5h-3.9z"/> + <path fill="url(#aJ)" fill-rule="nonzero" d="M120.2 185.3c-.6.5-1.1 1-1.5 1.5h3.4c.6-.5 1.3-1 2-1.5h-3.9z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M115.4 185.3h4.8l3.9-.1 5.8-.1h-29.6l.6.5"/> + <path fill="url(#aK)" fill-rule="nonzero" d="M115.4 185.3h4.8l3.9-.1 5.8-.1h-29.6l.6.5"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M100.1 184.9l-.1-.1c-.3-.5-.6-1-.7-1.6l-.1.9.9.8z"/> + <path fill="url(#aL)" fill-rule="nonzero" d="M100.1 184.9l-.1-.1c-.3-.5-.6-1-.7-1.6l-.1.9.9.8z"/> + <path fill="#FAFAFA" fill-rule="nonzero" d="M138.4 177.9h-33.8l-4.4.2-.1.8c.1-.2.3-.4.5-.5l.2-.2H141v-.3h-2.6z"/> + <path fill="url(#aM)" fill-rule="nonzero" d="M138.4 177.9h-33.8l-4.4.2-.1.8c.1-.2.3-.4.5-.5l.2-.2H141v-.3h-2.6z"/> + <path fill="url(#aN)" fill-rule="nonzero" d="M97.4 187.5c.5.3 1.5.6 3.1.7h.7c.3 0 .5-.1.6-.3l.3.2h41c.6 0 1.1-.2 1.4-.6.2-.2.4-.5.4-.7 0-.1.1-.3.1-.4v-1.1c0-1-.8-1.7-1.9-1.7h-1v-4h1c.6 0 1.1-.2 1.5-.7.4-.4.5-1 .3-1.5l-.1-.2-.2-.7c0-.1-.1-.3-.2-.4-.3-.6-.9-.9-1.7-.9h-40l-.5.4c-.3-.2-.7-.5-1.2-.5-1.2 0-2.9.2-3.8.8-.4.3-.8.8-1.1 1.5-.3.7-.6 1.5-.7 2.2-.2.8-.3 1.5-.3 2 0 2.1.6 4 1.6 5.2.2.3.4.5.7.7zm-.4-7.8c.2-.9.5-1.8.9-2.2l.3-.3c.4-.3 1.5-.5 2.8-.5.1.1.3.2.3.3.2.2.4.4.7.5l.4.1.9-.8h39. [...] + <path fill="#FFF" fill-rule="nonzero" d="M190.1 151.1c-8.6-5.4-23.3-5.4-23.5-5.4-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6.1 0 7.6-.1 15.1 1.8 5.8 1.5 10 3.7 12.6 6.7h.2c-9.2-10.9-26.7-9.7-26.9-9.6-.3 0-.6-.2-.6-.5s.2-.6.5-.6c.2 0 15.7-1.1 25.6 7.7-2.1-2.3-5.5-5.2-10.1-7.3-6.7-2.9-14.7-1.9-17-1.5l-1.2 5.3 25.1 4.3c0 .3.1.3.2.3zM210.5 142.3c-5 2.4-8.1 5.4-10 7.7 8.8-8.7 22.6-8.9 22.7-8.9.3 0 .6.2.6.6 0 .3-.2.6-.6.6-.2 0-15.1.2-23.3 10 .2-.1.4-.2.6-.4 2.3-2.2 5.4-4 9.5-5.5 6.9-2.5 14.1-3.1 14.2- [...] + <path fill="url(#aO)" fill-rule="nonzero" d="M230.9 147v-.5c-.1-.3-.2-.6-.4-.8-.3-.4-.8-.5-1.3-.5h-.2c-.7-2.6-1.7-6.5-1.9-7.1-.1-.3-.4-.6-.7-.7-1.2-.5-10.5-.2-16.7 2.9-7.5 3.7-11.2 8.5-12.7 11-1.8-2.6-6-7.4-13.1-10.5-8.6-3.7-18.7-1.6-19.1-1.5-.4.1-.8.4-.8.8l-1.4 6.1h-.3c-.5 0-.9.2-1.3.5-.3.3-.5.8-.5 1.3l.3 4.3h.5l.4.1.5 4.9c1.6-.1 6-.5 6.5-.5.8 0 1.3.5 1.4 1.4.2 1.9-1.9 5-7.1 6.2-.3.1-.9.6-1.1.8-.1.1.2.6-.1.8l.2 2.7.1.8.1 1.2 1.2 15.4c.1.9.8 1.5 1.6 1.5l24.9 3.6c.9.9 2.9 2.3 6.2 2.3h [...] + <path fill="url(#aP)" fill-rule="nonzero" d="M223.7 156.1c-.2-.2-.6-.3-.9-.3l-11.5 1.8c-.5.1-.9.5-.9 1.1l-.1 5.5c0 .3.1.6.4.9.2.2.5.3.7.3h.2l10.6-1.6c-.4-.9-.5-1.8-.4-2.4.1-.8.6-1.4 1.4-1.4.1 0 .5 0 .9.1l.1-3.1c-.2-.4-.3-.7-.5-.9z"/> + <path fill="url(#aQ)" fill-rule="nonzero" d="M223.7 156.1c-.2-.2-.6-.3-.9-.3l-11.5 1.8c-.5.1-.9.5-.9 1.1l-.1 5.5c0 .3.1.6.4.9.2.2.5.3.7.3h.2l10.6-1.6c-.4-.9-.5-1.8-.4-2.4.1-.8.6-1.4 1.4-1.4.1 0 .5 0 .9.1l.1-3.1c-.2-.4-.3-.7-.5-.9z"/> + <path fill="#FFF" fill-rule="nonzero" d="M162.8 163.3c4.7-1 6.4-3.7 6.3-5 0-.4-.2-.4-.3-.4-.4 0-4.4.3-6.9.6h-.5l-.6-5.1c-.9 0-3.2.4-5.5 2.6-1.4 1.3-1.7 3.1-.9 4.6.9 2 3.7 3.8 8.4 2.7z"/> + <path fill="url(#aR)" fill-rule="nonzero" d="M161.7 166.1c.1 0 .2 0 .2-.1.3-.2 0-.7.1-.8.2-.2.8-.7 1.1-.8 5.2-1.1 7.3-4.3 7.1-6.2-.1-.8-.6-1.4-1.4-1.4-.5 0-4.9.4-6.5.5l-.5-4.9-.4-.1h-.5c-.8 0-2.3.2-4 1.2-.7.4-1.5 1-2.3 1.7-.8.7-1.3 1.6-1.5 2.5-.2.8-.2 1.6 0 2.4.1.3-.8-.1-1 .1v.1c-.1.9.5 2.4 2 3.7 1.4 1.5 3.9 2.5 7.6 2.1zm-6.5-9.9c2.3-2.3 4.6-2.6 5.5-2.6l.6 5.1h.5c2.6-.2 6.5-.6 6.9-.6.1 0 .2 0 .3.4.1 1.2-1.6 3.9-6.3 5-4.7 1-7.5-.7-8.5-2.5-.7-1.7-.3-3.4 1-4.8z"/> + <path fill="#FFF" fill-rule="nonzero" d="M231.1 156.6l-.6 5.1h-.5c-2.6-.2-6.5-.6-6.9-.6-.1 0-.2 0-.3.4-.1 1.2 1.6 3.9 6.3 5 4.7 1 7.5-.7 8.5-2.5.8-1.5.5-3.3-.9-4.6-2.5-2.4-4.7-2.7-5.6-2.8z"/> + <path fill="url(#aS)" fill-rule="nonzero" d="M236.7 157.9c-3.2-2.7-6.1-2.4-6.2-2.4h-.5l-.5 4.9c-1.2-.1-4-.3-5.5-.5-.5 0-.8-.1-.9-.1-.8 0-1.3.5-1.4 1.4-.1.6.1 1.5.4 2.4.8 1.9 2.7 4 6.2 4.7 1 .2-1.2 0-.4.3.4.1.7.2 1 .3.3.1.6.2 1 .2 2.8.5 5.1-.1 6.4-1.1.4-.3.8-.6 1-1 .4-.5.7-1.6.9-2.2.1-.2.2-.4.2-.5.8-1.6.7-3.3-.2-4.8-.2-.4-.5-.7-.9-1.1-.2-.1-.4-.3-.6-.5zm.8 6c-1 1.8-3.8 3.5-8.5 2.5s-6.4-3.7-6.3-5c0-.4.2-.4.3-.4.4 0 4.4.3 6.9.6h.5l.6-5.1c.9 0 3.2.4 5.5 2.6 1.5 1.5 1.8 3.2 1 4.8z"/> + <path fill="url(#aT)" fill-rule="nonzero" d="M189.7 154.7l.3 33.2s1.1 2.2 6.4 2.8c5.3.6 6.7-3.1 6.7-3.1l.8-33.7s-2.5 2.2-6.7 2.5c-4.2.3-7.5-1.7-7.5-1.7z"/> + <path fill="url(#aU)" fill-rule="nonzero" d="M189.7 154.7l.3 33.2s1.1 2.2 6.4 2.8c5.3.6 6.7-3.1 6.7-3.1l.8-33.7s-2.5 2.2-6.7 2.5c-4.2.3-7.5-1.7-7.5-1.7z"/> + <path d="M-31-22h352v303H-31z"/> + </g> +</svg> diff --git a/browser/extensions/onboarding/content/img/figure_performance.svg b/browser/extensions/onboarding/content/img/figure_performance.svg new file mode 100644 index 0000000000000..f7c5c219aada8 --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_performance.svg @@ -0,0 +1 @@ +<svg width="297" height="245" viewBox="0 0 297 245" xmlns="http://www.w3.org/2000/svg"><title>performance</title><defs><linearGradient x1="-920.838%" y1="-294.992%" x2="891.374%" y2="366.984%" id="a"><stop stop-color="#FFFBCC" offset="0%"/><stop stop-color="#CEF7C6" offset="100%"/></linearGradient><linearGradient x1="-162.81%" y1="-242.422%" x2="179.364%" y2="239.183%" id="b"><stop stop-color="#FFFBCC" offset="0%"/><stop stop-color="#CEF7C6" offset="100%"/></linearGradient><linearGradien [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_private.svg b/browser/extensions/onboarding/content/img/figure_private.svg new file mode 100644 index 0000000000000..f90163e4b4d77 --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_private.svg @@ -0,0 +1 @@ +<svg width="289" height="237" viewBox="0 0 289 237" xmlns="http://www.w3.org/2000/svg"><title>private-browsing</title><defs><linearGradient x1="12.376%" y1="17.359%" x2="82.943%" y2="91.352%" id="a"><stop stop-color="#E60024" offset="0%"/><stop stop-color="#ED00B5" offset="51.53%"/><stop stop-color="#8000D7" offset="100%"/></linearGradient><linearGradient x1="-3.914%" y1=".14%" x2="98.417%" y2="106.522%" id="b"><stop stop-color="#E60024" offset="0%"/><stop stop-color="#ED00B5" offset="51 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_screenshots.svg b/browser/extensions/onboarding/content/img/figure_screenshots.svg new file mode 100644 index 0000000000000..f4930d09f7af5 --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_screenshots.svg @@ -0,0 +1,191 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="281" height="233"> + <defs> + <linearGradient id="a" x1="-26.7072552%" x2="121.200691%" y1="-8.21456664%" y2="115.364749%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="-171.534367%" x2="377.694136%" y1="-258.916232%" y2="507.082022%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="c" x1="-275.615152%" x2="393.814483%" y1="-214.880097%" y2="329.931438%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="d" x1="-71.2230562%" x2="141.268437%" y1="-46.5567621%" y2="122.213199%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="e" x1="-912.187374%" x2="706.872366%" y1="-223.131903%" y2="247.7375%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="f" x1="-636.509606%" x2="265.115932%" y1="-364.308744%" y2="178.753736%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="g" x1="-96.7324958%" x2="214.858961%" y1="-489.128132%" y2="600.29142%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="h" x1="-370.226425%" x2="176.655533%" y1="-420.236682%" y2="206.08556%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="i" x1="-1573.85207%" x2="2621.18334%" y1="-918.807829%" y2="1582.542%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="j" x1="-1977.10979%" x2="2217.92561%" y1="-1158.35597%" y2="1342.99386%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="k" x1="-635.169191%" x2="1018.69953%" y1="-1184.44408%" y2="1785.60576%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="l" x1="-278.76866%" x2="377.256589%" y1="-697.981967%" y2="835.635246%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="m" x1="-553.131633%" x2="647.619338%" y1="-1374.34047%" y2="1418.49315%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="n" x1="-450.59361%" x2="546.286439%" y1="-895.950857%" y2="958.91224%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="o" x1="-511.211278%" x2="295.07392%" y1="-745.273546%" y2="396.265912%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="p" x1="-871.182847%" x2="303.781403%" y1="-595.928571%" y2="241.5435%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="q" x1="-450.336951%" x2="307.764971%" y1="-505.416691%" y2="315.448433%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="r" x1="-2519.79056%" x2="1944.50093%" y1="-1090.70814%" y2="890.815528%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="s" x1="-134.127826%" x2="165.330874%" y1="-297.102666%" y2="260.202663%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="t" x1="-1132.52358%" x2="304.180944%" y1="-1559.01765%" y2="393.843988%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="u" x1="-1884.94918%" x2="1592.74001%" y1="-342.289711%" y2="381.222953%"> + <stop stop-color="#E6FCFF" offset="0%"/> + <stop stop-color="#B5F2FF" offset="100%"/> + </linearGradient> + <linearGradient id="v" x1="-109.932792%" x2="195.629347%" y1="-425.144051%" y2="431.622036%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="w" x1="-813.648281%" x2="368.736119%" y1="-1076.38789%" y2="459.249729%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="x" x1="-1092.12785%" x2="635.82518%" y1="-4587.46665%" y2="2425.66052%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="y" x1="-415.250984%" x2="1490.35841%" y1="-442.448072%" y2="1582.67684%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="z" x1="-167.167389%" x2="492.546376%" y1="-2085.55413%" y2="4392.09342%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="A" x1="-2989.85248%" x2="1926.86535%" y1="-1363.11821%" y2="921.90878%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + <linearGradient id="B" x1="-2586.45105%" x2="2652.41027%" y1="-792.93501%" y2="883.790987%"> + <stop stop-color="#00C8D7" offset="0%"/> + <stop stop-color="#008EA4" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd"> + <g fill="#D7D7DB" fill-rule="nonzero"> + <path d="M204.3 76.7h-77c-.6 0-1.1-.5-1.1-1.1 0-.6.5-1.1 1.1-1.1h77c.6 0 1.1.5 1.1 1.1 0 .6-.4 1.1-1.1 1.1zM193.9 71h-13.4c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h13.4c.3 0 .6.2.6.6 0 .4-.2.6-.6.6zM176.4 81.7H163c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h13.4c.3 0 .6.2.6.6 0 .4-.2.6-.6.6zm-22.2 0h-3.3c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h3.3c.3 0 .6.2.6.6 0 .4-.3.6-.6.6zm-7.8 0h-1.1c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h1.1c.3 0 .6.2.6.6 0 .4-.3.6-.6.6zm-11.2 0h-13.4c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h13.4c. [...] + </g> + <g fill-rule="nonzero"> + <path fill="#F9F9FA" d="M152.3 47.8h23.8s-7.4-16.6 8.3-18.8c14.1-1.9 19.6 12.5 19.6 12.5s1.7-8.3 10-6.7c8.3 1.6 14.3 14.8 14.3 14.8H249"/> + <path fill="#D7D7DB" d="M249.5 45.8H245c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h4.5c.3 0 .6.2.6.6-.1.4-.3.6-.6.6zm-14.5 0h-1.1c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h1.1c.3 0 .6.2.6.6 0 .4-.3.6-.6.6zm-5.6 0h-.6c-.2 0-.4-.1-.5-.3-.1-.2-.6-1.1-1.3-2.3-.2-.3-.1-.6.2-.8.3-.2.6-.1.8.2.6.9 1 1.7 1.2 2.1h.3c.3 0 .6.2.6.6 0 .4-.4.5-.7.5zm-52.9-.7H175c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h.6c-.1-.3-.2-.6-.4-1-.1-.3.1-.6.4-.7.3-.1.6.1.7.4.3 1 .6 1.7.6 1.7.1.2.1.4 0 .5-.1.1-.3.3-.4.3zm-10.4 0h-13.4c-.3 0-.6-.2 [...] + <path fill="#F9F9FA" d="M250.2 50.1h-97.9c-.6 0-1.1-.5-1.1-1.1 0-.6.5-1.1 1.1-1.1h97.9c.6 0 1.1.5 1.1 1.1 0 .6-.5 1.1-1.1 1.1z"/> + </g> + <g fill-rule="nonzero"> + <path fill="#F9F9FA" d="M49.3 29.4h13.2s-4.1-9.2 4.6-10.4c7.8-1.1 10.9 7 10.9 7s.9-4.6 5.6-3.8c4.6.9 8 8.3 8 8.3h11.5"/> + <path fill="#D7D7DB" d="M62.9 27.9H49.7c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h12.8s.1-.1.2-.1c.3-.1.6 0 .7.3l.1.1c.1.2.1.4 0 .5-.2.3-.4.4-.6.4zm36.6-.1h-3.3c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h3.3c.3 0 .6.2.6.6 0 .3-.3.6-.6.6zm-20.9-3.6h-.2c-.3-.1-.5-.4-.4-.7.3-.9 1.5-4 4.9-4 .4 0 .8 0 1.2.1 1.8.3 3.6 1.5 5.4 3.4.2.2.2.6 0 .8-.2.2-.6.2-.8 0-1.6-1.7-3.2-2.7-4.8-3-.4-.1-.7-.1-1-.1-2.6 0-3.5 2.2-3.8 3.2-.1.1-.3.3-.5.3zm-15.2-4.9c-.1 0-.3-.1-.4-.2-.2-.2-.2-.6 0-.8.8-.8 1.8-1.4 3.1-1.7.3-.1.6.1 [...] + <path fill="#F9F9FA" d="M104 31.6H49.6c-.6 0-1.1-.5-1.1-1.1 0-.6.5-1.1 1.1-1.1H104c.6 0 1.1.5 1.1 1.1 0 .6-.5 1.1-1.1 1.1z"/> + </g> + <g fill-rule="nonzero"> + <path fill="#FFF" d="M19.6 169.1c-2.8 0-5-2.2-5-4.8V46c0-3 2.4-5.4 5.4-5.4h127c3 0 5.4 2.4 5.4 5.4v118.3c0 2.6-2.3 4.8-5 4.8H19.6z"/> + <path fill="#D7D7DB" d="M146.9 41.8c2.3 0 4.2 1.9 4.2 4.2v118.3c0 2-1.8 3.7-3.9 3.7H19.6c-2.2 0-3.9-1.6-3.9-3.7V46c0-2.3 1.9-4.2 4.2-4.2h127zm0-2.2h-127c-3.6 0-6.5 2.9-6.5 6.5v118.3c0 3.3 2.8 5.9 6.2 5.9h127.6c3.4 0 6.2-2.7 6.2-5.9V46c0-3.5-2.9-6.4-6.5-6.4z"/> + </g> + <path fill="#D7D7DB" fill-rule="nonzero" d="M145.8 62.9V161c0 1-.1 1.2-.1 1.2s-.2.1-1.2.1h-122c-1 0-1.2-.1-1.2-.1s-.1-.2-.1-1.2V62.9h124.6zm1.1-1.2H20v99.2c0 2 .4 2.5 2.5 2.5h122c2 0 2.5-.4 2.5-2.5V61.7h-.1z"/> + <g fill="#D7D7DB" fill-rule="nonzero"> + <circle cx="3.8" cy="3.7" r="2.9" transform="translate(23 48)"/> + <circle cx="3" cy="3.7" r="2.9" transform="translate(33 48)"/> + <path d="M115.3 54.9H51.5c-1.7 0-3.1-1.4-3.1-3.1v-.3c0-1.7 1.4-3.1 3.1-3.1h63.8c1.7 0 3.1 1.4 3.1 3.1v.3c0 1.8-1.4 3.1-3.1 3.1z"/> + <g> + <circle cx="3.8" cy="3.7" r="2.9" transform="translate(127 48)"/> + <circle cx="3.1" cy="3.7" r="2.9" transform="translate(137 48)"/> + </g> + </g> + <g transform="translate(149 84)"> + <ellipse cx="42.7" cy="142" fill="#EDEDF0" fill-rule="nonzero" rx="42.5" ry="6.5"/> + <path fill="#F9F9FA" fill-rule="nonzero" d="M121.2 99.6c-1.3-3.1-4.3-5.2-7.7-5.2-.7 0-1.4.1-2.1.3-.8 0-3.1-.3-7.2-2-1.7-.7-4.8-3.9-8.4-10.5 5.2-19.9 5.5-36.8.7-50.3-.4-1-.9-2.1-1.5-3.2l-.3-1.4 2-1.7c1.6-1.4 2.3-3.5 1.7-5.6-.3-1.2-1-2.2-2-2.9 0-.3 0-.6-.1-.9-.4-2.3-2.2-4.1-4.5-4.4-.4-.1-10.6-1.7-17.1-1.7h-.4l-1.7-2.8C70 3.1 65.5.6 60.5.6c-2.6 0-5.2.7-7.5 2.1-2.6 1.6-4.5 3.9-5.7 6.7-6 .7-12.1 2.3-18.2 4.7l-3.4-1.4c-1.7-.7-3.5-1.1-5.4-1.1-5.8 0-10.9 3.5-13.1 8.8-2.7 6.6-.1 14 5.8 17.5 [...] + <path d="M115.2 101.4c-.4-.9-1.5-1.4-2.4-1-.2.1-.6.1-1.2.1-1.4 0-4.6-.3-9.8-2.4-5.5-2.2-10.3-10.6-12.7-15.5-.1-.2-.1-.5-.1-.8 5.4-19.5 5.9-35.8 1.4-48.4-.3-.8-.7-1.8-1.3-2.7-.1-.1-.1-.2-.1-.3L87.7 25c-.1-.4 0-.8.4-1.1l2.6-2.2-5.8-.9c-.5-.1-.9-.4-.9-.9s.2-.9.6-1.2l3-1.6c-3.5-.5-8.9-1.1-12.7-1.1-1.1 0-2 .1-2.6.2l-.4.1c-.4.1-.9-.1-1.1-.5l-3.4-5.5C66 8 63.5 6.7 60.9 6.7c-1.4 0-2.8.4-4.1 1.2-2.2 1.4-3.5 3.7-3.6 6.3 0 .6-.5 1-1.1 1.1-7.2.4-14.7 2.2-22.2 5.4-.3.1-.6.1-.9 0l-5.4-2.2c-.9-.4 [...] + <path fill="url(#a)" fill-rule="nonzero" d="M114.6 98c-.8-2.1-2.9-3.4-5.1-3.4-.6 0-1.1.1-1.7.3-.7 0-3.4 0-8.7-2.2-3-1.2-6.8-6-10.3-12.8 5.4-19.8 5.7-36.5 1-49.7-.3-1-.8-2-1.4-3l-.9-3.4 3.3-2.8c.8-.7 1.1-1.7.8-2.7-.3-1-1.1-1.7-2.1-1.9l-.7-.1c.5-.6.8-1.4.6-2.2-.2-1.1-1-2-2.1-2.1-.1 0-10.3-1.6-16.7-1.6-.7 0-1.3 0-1.9.1l-2.6-4.2C64 2.9 60.4.9 56.4.9c-2.1 0-4.2.6-6 1.7-2.5 1.6-4.3 4.1-5 6.9-6.6.6-13.5 2.3-20.3 5.1l-4.4-1.8c-1.4-.6-2.8-.8-4.3-.8-4.7 0-8.8 2.8-10.6 7.1-2.4 5.8.4 12.5 6.2 [...] + <path fill="url(#b)" fill-rule="nonzero" d="M36.6 40.6c-1.1 0-2.2-.2-3.3-.7l-16.2-6.6c-4.5-1.8-6.7-7-4.8-11.5 1.8-4.5 7-6.7 11.5-4.8L40 23.6c4.5 1.8 6.7 7 4.8 11.5-1.4 3.4-4.7 5.5-8.2 5.5z"/> + <path fill="url(#c)" fill-rule="nonzero" d="M70.8 39.3c-2.9 0-5.8-1.5-7.5-4.2L53.1 18.6c-2.6-4.1-1.3-9.6 2.8-12.1C60 3.9 65.5 5.2 68 9.3l10.2 16.5c2.6 4.1 1.3 9.6-2.8 12.1-1.4 1-3 1.4-4.6 1.4z"/> + <path fill="url(#d)" fill-rule="nonzero" d="M28.6 19.4c-2.2.9-12.8 10.5-11.1 37.1 1.7 26.2-21.6 21.8-3.8 53.4 3.9 6.9 50.2 17.7 58.6 12.7 2.5-1.5 31.6-54.6 19.1-89.8-4.1-11.5-28.5-28-62.8-13.4z"/> + <path fill="url(#e)" fill-rule="nonzero" d="M14.3 87.5s-2.6 17.8-1.7 26.6c1 8.8 3.3 13.7 5.1 12.8 1.7-.8 6.2-26.8 6.2-26.8l-9.6-12.6z"/> + <path fill="url(#f)" fill-rule="nonzero" d="M80.7 103s-5.5 17.1-10.3 24.6c-4.8 7.5-9.1 10.8-10.2 9.3-1.2-1.5 6.2-26.8 6.2-26.8l14.3-7.1z"/> + <path fill="url(#g)" fill-rule="nonzero" d="M33.5 19c7.8-4 28.9-2.7 38.4-4.1C77 14.1 91 16.3 91 16.3l-6 3.2 8.2 1.2-4.5 3.8 1.8 7.3-1.3-.7-46.3-12.8-9.4.7z"/> + <path fill="url(#h)" fill-rule="nonzero" d="M111.4 105.1c-2.3 0-6-.6-11.5-2.8-10-4-16.7-20.9-17.4-22.9-.6-1.5.2-3.2 1.7-3.8 1.5-.6 3.2.2 3.8 1.7 1.7 4.5 7.7 16.9 14.1 19.5 7.1 2.9 10.2 2.3 10.2 2.3 1.5-.6 3.2.1 3.8 1.6.6 1.5-.1 3.2-1.6 3.8-.4.3-1.4.6-3.1.6z"/> + <path fill="#FFF" fill-rule="nonzero" d="M35.4 29.8c-8.3 5.5-3.2 72.6 2.7 79.8 9.5 11.8 31.7 9.3 34.6 3 1.1-2.3 26-48.2 14.3-79.8-3-8-22.5-22.3-51.6-3z"/> + <path fill="url(#i)" fill-rule="nonzero" d="M50.3 43.8c.9.2 1.4 1.1 1.2 1.9l-.8 3.5c-.2.9-1.1 1.4-1.9 1.2-.9-.2-1.4-1.1-1.2-1.9l.8-3.5c.2-.9 1.1-1.4 1.9-1.2z"/> + <path fill="url(#j)" fill-rule="nonzero" d="M81.4 44.8c.9.2 1.4 1.1 1.2 1.9l-.8 3.5c-.2.9-1.1 1.4-1.9 1.2-.9-.2-1.4-1.1-1.2-1.9l.8-3.5c.2-.9 1-1.4 1.9-1.2z"/> + <path fill="url(#k)" fill-rule="nonzero" d="M48.9 57.6c-.5 0-1-.1-1.5-.2-3.5-.8-4.7-3.9-4.7-4.1-.3-.8.1-1.6.9-1.9.8-.3 1.6.1 1.9.9 0 .1.7 1.8 2.6 2.2 1.9.5 3.3-.8 3.3-.8.6-.6 1.5-.5 2.1 0 .6.6.5 1.5 0 2.1-.2.1-2 1.8-4.6 1.8z"/> + <path fill="url(#l)" fill-rule="nonzero" d="M56.6 69.2c-.8 0-1.4-.6-1.5-1.3-.1-.8.5-1.5 1.3-1.6 8.9-.7 17.1-2.5 18-3.8 1-1.7 1.2-4 1.2-4.1 0-.8.7-1.4 1.4-1.4.8 0 1.4.5 1.5 1.3.1 1.3.6 3.4 1.2 4.1 1.1 1.3 2.3 1.2 2.3 1.2.8 0 1.5.6 1.6 1.4.1.8-.6 1.5-1.4 1.6-1 .1-3.2-.3-4.8-2.3-.1-.2-.3-.4-.4-.6-.1.1-.1.2-.2.3-2 3.3-14.8 4.7-20.3 5.2h.1z"/> + <g fill-rule="nonzero"> + <path fill="url(#m)" d="M2.4 4.3C1.3 5 7.7 8.2 8.6 8.2c1.3 0 7.8-2.8 7.6-5C16 2.1 6.8 1.3 2.4 4.3z" transform="translate(70 52)"/> + <path fill="url(#n)" d="M8.6 9.7C7.5 9.7 1.5 7 .9 5c-.2-.8.1-1.5.7-2C5.8.2 13.9.3 16.3 1.4c1 .4 1.2 1.1 1.3 1.6.1.9-.2 1.7-1 2.6-1.8 2.1-6.4 4.1-8 4.1zm-3.9-5c1.3.8 3.5 1.9 4.1 2 .9-.1 4.3-1.7 5.5-2.8-2-.4-6.5-.5-9.6.8z" transform="translate(70 52)"/> + </g> + <g fill-rule="nonzero"> + <path fill="#C8C8CC" d="M115 92.8l-7.2.1-.5-40.7c0-3.3 2.5-6.1 5.7-6.3.3 0 .5.2.5.4l1.5 46.5z"/> + <path fill="#E1E1E6" d="M130.1 53.3c.2-.2.5-.1.7.1 1.9 2.7 1.4 6.4-1.1 8.5l-31.3 26-4.6-5.5 36.3-29.1z"/> + <path fill="url(#o)" d="M.7 10c-.4 2.6.2 5.2 1.9 7.1.8 1 1.8 1.7 2.9 2.3 3.5 1.6 7.8 1 11-1.7.2-.2.5-.4.7-.6l10.1-8.4c.4-.4.7-.9.8-1.4.1-.6-.1-1.1-.5-1.5l-2.9-3.4c-.2-.2-.4-.4-.7-.6-.2-.1-.5-.2-.7-.2-.6-.1-1.1.1-1.5.5l-2.9 2.4c-.1-.2-.2-.3-.4-.5-.8-1-1.8-1.7-2.9-2.3-3.5-1.6-7.8-1-11 1.7C2.5 5.1 1.2 7.5.7 10zm6.6-3.4c1.9-1.6 4.5-2.1 6.5-1.1.6.3 1.1.7 1.5 1.1 1.4 1.6 1.3 4.1.1 6.1-.5.7-1.1 1.4-2 2.1-1.9 1.3-4.2 1.5-5.9.7-.6-.3-1.1-.7-1.5-1.1-.8-1-1.2-2.4-.9-3.8 0-1.5.9-2.9 2.2-4z" [...] + <path fill="url(#p)" d="M0 2.5l.2 13.2v.9c.1 4.1 2.3 7.8 5.7 9.4 1.2.6 2.5.9 3.8.8 5.1-.1 9.3-4.7 9.2-10.4-.1-4.1-2.3-7.8-5.7-9.4-1.2-.6-2.5-.9-3.8-.8h-.6V2.4c0-.8-.5-1.5-1.2-1.9C7.3.4 7 .3 6.7.3L2.2.4C1.6.4 1.1.6.7 1 .2 1.4 0 2 0 2.5zm11.3 8.3c1.9.9 3.2 3.1 3.3 5.6 0 3.4-2.2 6.1-5 6.2-.7 0-1.3-.1-1.9-.4-1.8-.8-3-2.7-3.2-4.9v-.1c0-1.2.1-2.1.3-2.9.7-2.2 2.5-3.9 4.7-3.9.5 0 1.2.1 1.8.4z" transform="translate(107 83)"/> + <path fill="#C8C8CC" d="M111.3 70.6c1.3.1 2.2 1.3 2.1 2.5-.1 1.3-1.3 2.2-2.5 2.1-1.3-.1-2.2-1.3-2.1-2.5.1-1.2 1.2-2.2 2.5-2.1z"/> + </g> + <path fill="url(#q)" fill-rule="nonzero" d="M1.4 2.1L.3 5.7c-1 3.1.7 6.4 3.8 7.4 3.1 1 6.4-.7 7.4-3.8L14.4.1l-13 2z" transform="translate(57 67)"/> + <path fill="url(#r)" fill-rule="nonzero" d="M63.3 74.7h-.2c-.4-.1-.6-.5-.5-.9l2.2-6.8c.1-.4.5-.6.9-.5.4.1.6.5.5.9L64 74.2c-.1.3-.4.5-.7.5z"/> + <path fill="url(#s)" fill-rule="nonzero" d="M58.7 98.1c-17.5 0-33-27.8-33.6-29-.8-1.4-.3-3.2 1.2-4 1.4-.8 3.2-.3 4 1.2 4.2 7.6 17.5 27 29.4 25.9 15.2-1.4 22.4-6.9 22.4-7 1.3-1 3.1-.8 4.1.5 1 1.3.8 3.1-.4 4.1-.3.3-8.5 6.7-25.6 8.2-.5.1-1 .1-1.5.1z"/> + <path fill="url(#t)" fill-rule="nonzero" d="M112.5 97.8s-8 3.2-8.1 5.9c-.1 2.7 8.2 6 11.8.7 3.6-5.2-2.3-7.2-3.7-6.6z"/> + <path fill="url(#u)" fill-rule="nonzero" d="M30.5 65.3s.7 5.9 4.4 9.2c3.7 3.3-4.8 8.1-4.4 15.4.4 7.4 0-24.6 0-24.6z"/> + <path fill="url(#v)" fill-rule="nonzero" d="M58.8 98.9h-1.1C44 98.5 32 81 31.5 80.2c-.2-.3-.1-.8.2-1 .3-.2.8-.1 1 .2.1.2 12.1 17.7 25 18 12.8.3 25.3-6.2 27.1-7.7.5-.4.9-2.6.2-3.7-.7-1-2.4-.3-3.6.5-.3.2-.8.1-1-.2-.2-.3-.1-.8.2-1 3.4-2.1 4.9-.9 5.6-.1 1.2 1.6.8 4.7-.4 5.7-1.2 1-13.4 8-27 8z"/> + <path fill="url(#w)" fill-rule="nonzero" d="M110.8 108.3c-1.3 0-2.8-.3-4.4-1.3-1.9-1-2.8-2.2-2.7-3.6.2-2.7 4.7-4.5 5.2-4.7.4-.1.8 0 1 .4.1.4 0 .8-.4 1-1.6.6-4.2 2.1-4.3 3.5-.1.9 1 1.7 1.9 2.2 2.2 1.2 4.3 1.4 6.1.6 2.1-1 3.1-2.8 3.2-3.2.1-.6.5-2.4-.5-3.5-.7-.8-2.1-1.1-4.1-.9-.4 0-.8-.3-.8-.7 0-.4.3-.8.7-.8 2.5-.2 4.3.2 5.3 1.4 1.5 1.6 1 4 .8 4.7-.2.9-1.6 3.2-4 4.3-.8.3-1.8.6-3 .6z"/> + <path fill="url(#x)" fill-rule="nonzero" d="M61.1 125.5c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7 3.2 0 8.1-1 8.2-1 .4-.1.8.2.9.6.1.4-.2.8-.6.9-.2-.1-5.1.9-8.5.9z"/> + <path fill="url(#y)" fill-rule="nonzero" d="M23 25.4h-.2c-.4-.1-.6-.5-.5-.9.2-.7 2.4-5 7.8-7.4.4-.2.8 0 1 .4.2.4 0 .8-.4 1-4.7 2-6.7 5.8-6.9 6.4-.2.3-.5.5-.8.5z"/> + <path fill="url(#z)" fill-rule="nonzero" d="M68.5 14.8c-8.9 0-18.2-1.2-18.3-1.2-.4-.1-.7-.4-.6-.8.1-.4.4-.7.8-.6.1 0 14.1 1.8 24.1 1 .4 0 .8.3.8.7 0 .4-.3.8-.7.8-2 0-4 .1-6.1.1z"/> + <path fill="url(#A)" fill-rule="nonzero" d="M88.8 89h-.2c-.4-.1-.6-.5-.5-.9l2-6c.1-.4.5-.6.9-.5.4.1.6.5.5.9l-2 6c-.1.3-.4.5-.7.5z"/> + <path fill="url(#B)" fill-rule="nonzero" d="M21 119.1h-.1c-.4-.1-.7-.5-.6-.9l1.7-8.6c.1-.4.5-.7.9-.6.4.1.7.5.6.9l-1.7 8.6c-.2.4-.5.6-.8.6z"/> + </g> + <path fill="#D7D7DB" fill-rule="nonzero" d="M70.8 82.4c-3.7 0-6.6 3-6.6 6.6h6.6v-6.6zm20 0h-6.6V89h6.6v-6.6zm13.3 0V89h6.6c0-3.6-3-6.6-6.6-6.6zm-23.3 0h-6.6V89h6.6v-6.6zm19.9 0h-6.6V89h6.6v-6.6zm3.4 16.6h6.6v-6.6h-6.6V99zm0 20c3.7 0 6.6-3 6.6-6.6h-6.6v6.6zm0-10h6.6v-6.6h-6.6v6.6zm-1.5-7.2c-2.1-3-6.2-3.7-9.3-1.6l-12.7 9.4-6.5-4.6c0-.3.1-.6.1-1 0-2.7-1.3-5.1-3.3-6.6v-5h-6.6v3.5c-3.8.8-6.6 4.1-6.6 8.1 0 4.6 3.7 8.3 8.3 8.3 1.8 0 3.5-.6 4.8-1.6l4.1 2.9-4.6 3.3c-1.3-.8-2.7-1.2-4.3-1.2-4.6 [...] + <g fill="#D7D7DB" fill-rule="nonzero"> + <path d="M17.5 26.8l-.1-.1.1.1zM266.5 1.5v4.4c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V3h-2.9c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5h4.4c.8 0 1.5.7 1.5 1.5zM266.5 14.4v8.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5zm0 17V40c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.6 1.5 1.4zm0 17.1V57c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5zm0 17V74c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1 [...] + </g> + <path d="M-18-32h352v303H-18z"/> + </g> +</svg> diff --git a/browser/extensions/onboarding/content/img/figure_singlesearch.svg b/browser/extensions/onboarding/content/img/figure_singlesearch.svg new file mode 100644 index 0000000000000..9be029397ccfe --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_singlesearch.svg @@ -0,0 +1 @@ +<svg width="303" height="253" viewBox="0 0 303 253" xmlns="http://www.w3.org/2000/svg"><title>search</title><defs><linearGradient x1="-18.632%" y1="-397.383%" x2="117.795%" y2="492.152%" id="a"><stop stop-color="#00C8D7" offset="0%"/><stop stop-color="#0A84FF" offset="100%"/></linearGradient><linearGradient x1="-312.046%" y1="-3945.649%" x2="293.266%" y2="2768.992%" id="b"><stop stop-color="#00C8D7" offset="0%"/><stop stop-color="#0A84FF" offset="100%"/></linearGradient><linearGradient x [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_sync.svg b/browser/extensions/onboarding/content/img/figure_sync.svg new file mode 100644 index 0000000000000..74562d37236da --- /dev/null +++ b/browser/extensions/onboarding/content/img/figure_sync.svg @@ -0,0 +1 @@ +<svg width="279" height="212" viewBox="0 0 279 212" xmlns="http://www.w3.org/2000/svg"><title>sync</title><defs><linearGradient x1="-424.525%" y1="-219.797%" x2="201.215%" y2="136.157%" id="a"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradient x1="-1416.558%" y1="-1417.275%" x2="631.855%" y2="631.14%" id="b"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradient x1= [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_addons.svg b/browser/extensions/onboarding/content/img/icons_addons.svg new file mode 100644 index 0000000000000..6b27dea39252a --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_addons.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16"><title>Icons / Extension</title><g fill="none"><path d="M0 0h16v16H0z"/><path d="M22.5 16c-1 0-1 1-1.7 1-.5 0-.8-.3-.8-.7V13c0-.6-.4-1-1-1h-3.2c-.5 0-.8-.3-.8-.7 0-.8 1-.8 1-1.8 0-.9-.9-1.5-2-1.5s-2 .6-2 1.5c0 1 1 1 1 1.8 0 .4-.3.7-.7.7H9c-.6 0-1 .4-1 1v2.3c0 .4.3.7.8.7.7 0 .7-1 1.7-1 .9 0 1.5.9 1.5 2s-.6 2-1.5 2c-1 0-1-1-1.7-1-.5 0-.8.3-.8.8V23c0 .6.4 1 1 1h3.3c.4 0 .7-.3.7-.7 0-.8-1-.8-1-1.8 0-.9.9-1.5 2 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_customize.svg b/browser/extensions/onboarding/content/img/icons_customize.svg new file mode 100644 index 0000000000000..ae0a9409fa5c7 --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_customize.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>Glyph / Customize</title><g id="Symbols" fill="none" fill-rule="evenodd"><g id="Glyph-/-Customize" fill-rule="nonzero" fill="#3E3D40"><path d="M4 10c-.886.002-1.665.59-1.91 1.44 0 .01-.015.015-.018.025-.362 1.135-.705 2.11-1.76 2.573l-.022.012-.024.012c-.162.086-.265.254-.266.438 0 .276.224.5.5.5 1.74.12 3.46-.414 4.825-1.5.006-.006.007-.013.013-.02.62-.55.832-1.428.534-2.202C5.575 10.504 4.83 9.995 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_default.svg b/browser/extensions/onboarding/content/img/icons_default.svg new file mode 100644 index 0000000000000..235f7d65b6856 --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_default.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><title>default-browser-16</title><path fill="context-fill" d="M8,6s0-4,3.5-4S15,5,15,6c0,4.5-7,9-7,9Z"/><path fill="context-fill" d="M8,6S8,2,4.5,2,1,5,1,6c0,4.5,7,9,7,9L9,9Z"/></svg> diff --git a/browser/extensions/onboarding/content/img/icons_library.svg b/browser/extensions/onboarding/content/img/icons_library.svg new file mode 100644 index 0000000000000..064c2e6194867 --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_library.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg width="92px" height="92px" viewBox="0 0 92 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Tip / Icon / Library</title><desc>Created with Sketch.</desc><defs></defs><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Tip-/-Icon-/-Library" fill-rule="nonzero" fill="#0C0C0D"><g id="Icon-/-Library-/-Web"><path d="M28.7405828,17.2350375 C25.5662458,17.2350375 22 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_performance.svg b/browser/extensions/onboarding/content/img/icons_performance.svg new file mode 100644 index 0000000000000..ad23ba27400ca --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_performance.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M8 1a8.009 8.009 0 0 0-8 8 7.917 7.917 0 0 0 .78 3.43 1 1 0 1 0 1.8-.86A5.943 5.943 0 0 1 2 9a6 6 0 1 1 11.414 2.571 1 1 0 1 0 1.807.858A7.988 7.988 0 0 0 8 1z"/><path fill="context-fill" d="M11.769 7.078a.5.5 0 0 0-.69.153L8.616 11.1a2 2 0 1 0 .5 3.558 2.011 2.011 0 0 0 .54-.54 1.954 1.954 0 0 0-.2-2.479l2.463-3.871a.5.5 0 0 0-.15-.69z"/></svg> diff --git a/browser/extensions/onboarding/content/img/icons_private.svg b/browser/extensions/onboarding/content/img/icons_private.svg new file mode 100755 index 0000000000000..7d4d2c416801c --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_private.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16"><title>Icons / Private Browsing</title><g fill="none"><path d="M0 0h32v32H0z"/><path d="M20.4 20c-1.7 0-2.8-2-4.4-2-1.6 0-2.8 2-4.4 2-2 0-3.5-2-3.5-5.3-.1-2 .6-2.7 3.2-2.7s3.4 1.1 4.7 1.1c1.3 0 2.1-1.1 4.7-1.1s3.3.7 3.2 2.7c0 3.3-1.5 5.3-3.5 5.3zm-7.8-5.4c-1.6 0-2.3 1-2.3 1.2 0 .3 1.1.9 2.1.9 1.1 0 2.3-.4 2.3-.7-.2-1-1.1-1.6-2.1-1.4zm6.8 0c-1-.2-1.9.4-2.1 1.4 0 .3 1.2.7 2.3.7 1 0 2.1-.6 2.1-.9 0-.2-.7-1.2- [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_screenshots.svg b/browser/extensions/onboarding/content/img/icons_screenshots.svg new file mode 100644 index 0000000000000..8d219dce78b5e --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_screenshots.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg width="92px" height="92px" viewBox="0 0 92 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Tip / Icon / Screenshots</title><desc>Created with Sketch.</desc><defs></defs><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Tip-/-Icon-/-Screenshots" fill-rule="nonzero" fill="#0C0C0D"><g id="Icon-/-Screenshot-/-Web"><path d="M23.0526905,5.75 C16.7062659,5.75 11. [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_singlesearch.svg b/browser/extensions/onboarding/content/img/icons_singlesearch.svg new file mode 100644 index 0000000000000..3e06a38522881 --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_singlesearch.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16 "><title>Icons / Search</title><g fill="none"><path d="M0 0h32v32H0z"/><path d="M23.7 22.3l-4.8-4.8c1.8-2.5 1.4-6.1-1-8.1s-5.9-1.9-8.1.4c-2.3 2.2-2.4 5.7-.4 8.1 2 2.4 5.6 2.8 8.1 1l4.8 4.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4zM14 18c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4c0 1.1-.4 2.1-1.1 2.9-.8.7-1.8 1.1-2.9 1.1z" fill="#3E3D40"/></g></svg> \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_sync.svg b/browser/extensions/onboarding/content/img/icons_sync.svg new file mode 100644 index 0000000000000..286422275aa7a --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_sync.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16"><title> Icons / Sync</title><desc> Created with Sketch.</desc><g fill="none"><rect width="32" height="32"/><path d="M22 9C21.4 9 21 9.4 21 10L21 11.1C19.2 9.3 16.6 8.6 14.2 9.2 11.7 9.9 9.8 11.8 9.2 14.3 9.1 14.7 9.2 15 9.5 15.3 9.8 15.5 10.1 15.6 10.5 15.5 10.8 15.4 11.1 15.1 11.2 14.8 11.7 12.6 13.7 11 16 11 17.6 11 19 11.7 20 13L18 13C17.4 13 17 13.4 17 14 17 14.6 17.4 15 18 15L22 15C22.6 15 23 1 [...] diff --git a/browser/extensions/onboarding/content/img/icons_tour-complete.svg b/browser/extensions/onboarding/content/img/icons_tour-complete.svg new file mode 100644 index 0000000000000..173e72c332df3 --- /dev/null +++ b/browser/extensions/onboarding/content/img/icons_tour-complete.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch --> + <title>Tip / Check</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Tips-/-Navigation" transform="translate(-30.000000, -117.000000)" stroke-width="2"> + <g id="Group"> + <g id="Tip-/-Check" transform="translate(30.000000, 117.000000)"> + <circle id="Oval-2" stroke="#FFFFFF" fill="#33F70C" fill-rule="evenodd" cx="10" cy="10" r="9"></circle> + <polyline id="Path-31" stroke="#165866" stroke-linecap="round" stroke-linejoin="round" points="5.5 10.5 8.5 13.5 14.5 6.5"></polyline> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/watermark.svg b/browser/extensions/onboarding/content/img/watermark.svg new file mode 100644 index 0000000000000..c9345ed2ba1df --- /dev/null +++ b/browser/extensions/onboarding/content/img/watermark.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><title>newtab-firefox-gry</title><path d="M31.359,14.615h0c-.044-.289-.088-.459-.088-.459s-.113.131-.3.378A10.77,10.77,0,0,0,30.6,12.5a13.846,13.846,0,0,0-.937-2.411,10.048,10.048,0,0,0-.856-1.468q-.176-.263-.359-.51c-.57-.931-1.224-1.5-1.981-2.576a7.806,7.806,0,0,1-.991-2.685A10.844,10.844,0,0,0,25,4.607c-.777-.784-1.453-1.341-1.861-1.721C21.126,1.006,21.36.031,21.36.031h0S17.6,4.228,19.229,8.6a8.4,8.4,0, [...] diff --git a/browser/extensions/onboarding/content/onboarding-tour-agent.js b/browser/extensions/onboarding/content/onboarding-tour-agent.js new file mode 100644 index 0000000000000..d60a41b2c9f58 --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding-tour-agent.js @@ -0,0 +1,94 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals Mozilla */ + +(function() { +"use strict"; + +let onCanSetDefaultBrowserInBackground = () => { + Mozilla.UITour.getConfiguration("appinfo", config => { + let canSetInBackGround = config.canSetDefaultBrowserInBackground; + let btn = document.getElementById("onboarding-tour-default-browser-button"); + btn.setAttribute("data-cansetbg", canSetInBackGround); + btn.textContent = canSetInBackGround ? btn.getAttribute("data-bg") : btn.getAttribute("data-panel"); + }); +}; + +let onClick = evt => { + switch (evt.target.id) { + case "onboarding-tour-addons-button": + Mozilla.UITour.showHighlight("addons"); + break; + case "onboarding-tour-customize-button": + Mozilla.UITour.showHighlight("customize"); + break; + case "onboarding-tour-default-browser-button": + Mozilla.UITour.getConfiguration("appinfo", (config) => { + let isDefaultBrowser = config.defaultBrowser; + let btn = document.getElementById("onboarding-tour-default-browser-button"); + let msg = document.getElementById("onboarding-tour-is-default-browser-msg"); + let canSetInBackGround = btn.getAttribute("data-cansetbg") === "true"; + if (isDefaultBrowser || canSetInBackGround) { + btn.classList.add("onboarding-hidden"); + msg.classList.remove("onboarding-hidden"); + if (canSetInBackGround) { + Mozilla.UITour.setConfiguration("defaultBrowser"); + } + } else { + btn.disabled = true; + Mozilla.UITour.setConfiguration("defaultBrowser"); + } + }); + break; + case "onboarding-tour-library-button": + Mozilla.UITour.showHighlight("library"); + break; + case "onboarding-tour-private-browsing-button": + Mozilla.UITour.showHighlight("privateWindow"); + break; + case "onboarding-tour-singlesearch-button": + Mozilla.UITour.showMenu("urlbar"); + break; + case "onboarding-tour-sync-button": + let emailInput = document.getElementById("onboarding-tour-sync-email-input"); + if (emailInput.checkValidity()) { + Mozilla.UITour.showFirefoxAccounts(null, emailInput.value); + } + break; + case "onboarding-tour-sync-connect-device-button": + Mozilla.UITour.showConnectAnotherDevice(); + break; + } + let classList = evt.target.classList; + // On keyboard navigation the target would be .onboarding-tour-item. + // On mouse clicking the target would be .onboarding-tour-item-container. + if (classList.contains("onboarding-tour-item") || classList.contains("onboarding-tour-item-container")) { + Mozilla.UITour.hideHighlight(); // Clean up UITour if a user tries to change to other tours. + } +}; + +let overlay = document.getElementById("onboarding-overlay"); +overlay.addEventListener("submit", e => e.preventDefault()); +overlay.addEventListener("click", onClick); +overlay.addEventListener("keypress", e => { + let { target, key } = e; + let classList = target.classList; + if ((key == " " || key == "Enter") && + // On keyboard navigation the target would be .onboarding-tour-item. + // On mouse clicking the target would be .onboarding-tour-item-container. + (classList.contains("onboarding-tour-item") || classList.contains("onboarding-tour-item-container"))) { + Mozilla.UITour.hideHighlight(); // Clean up UITour if a user tries to change to other tours. + } +}); +let overlayObserver = new MutationObserver(mutations => { + if (!overlay.classList.contains("onboarding-opened")) { + Mozilla.UITour.hideHighlight(); // Clean up UITour if a user tries to close the dialog. + } +}); +overlayObserver.observe(overlay, { attributes: true }); +document.getElementById("onboarding-overlay-button").addEventListener("Agent:Destroy", () => Mozilla.UITour.hideHighlight()); +document.addEventListener("Agent:CanSetDefaultBrowserInBackground", onCanSetDefaultBrowserInBackground); + +})(); diff --git a/browser/extensions/onboarding/content/onboarding.css b/browser/extensions/onboarding/content/onboarding.css new file mode 100644 index 0000000000000..8f24314776341 --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding.css @@ -0,0 +1,589 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#onboarding-overlay * { + box-sizing: border-box; +} + +#onboarding-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + /* Ensuring we can put the overlay over elements using + z-index on original page */ + z-index: 20999; + color: #4d4d4d; + background: var(--newtab-overlay-color, rgb(245, 245, 247, 0.9)); /* #f7f7f5, 0.9 opacity */ + display: none; +} + +#onboarding-overlay.onboarding-opened { + display: block; +} + +#onboarding-overlay-button { + padding: 10px 0 0 0; + position: fixed; + cursor: pointer; + top: 4px; + inset-inline-start: 12px; + border: none; + /* Set to none so no grey contrast background in the high-contrast mode */ + background: none; + /* make sure the icon stay above the activity-stream searchbar */ + /* We want this always under #onboarding-overlay */ + z-index: 10; +} + +/* Keyboard focus styling */ +#onboarding-overlay-button:-moz-focusring { + outline: solid 2px rgba(0, 0, 0, 0.1); + -moz-outline-radius: 5px; + outline-offset: 5px; + transition: outline-offset 150ms; +} + +#onboarding-overlay-button > img { + width: 32px; + vertical-align: top; +} + +#onboarding-overlay-button::after { + content: " "; + border-radius: 50%; + margin-top: -1px; + margin-inline-start: -13px; + border: 2px solid #f2f2f2; + background: #0A84FF; + padding: 0; + width: 10px; + height: 10px; + min-width: unset; + max-width: unset; + display: block; + box-sizing: content-box; + float: inline-end; + position: relative; +} + +#onboarding-overlay-button:hover::after, +#onboarding-overlay-button.onboarding-speech-bubble::after { + background: #0060df; + font-size: 13px; + text-align: center; + color: #fff; + box-sizing: content-box; + font-weight: 400; + content: attr(aria-label); + border: 1px solid transparent; + border-radius: 2px; + padding: 10px 16px; + width: auto; + height: auto; + min-width: 100px; + max-width: 140px; + white-space: pre-line; + margin-inline-start: 4px; + margin-top: -10px; + box-shadow: -2px 0 5px 0 rgba(74, 74, 79, 0.25); +} + +#onboarding-overlay-button:dir(rtl)::after { + box-shadow: 2px 0 5px 0 rgba(74, 74, 79, 0.25); +} + +#onboarding-overlay-button-watermark-icon { + -moz-context-properties: fill; + fill: var(--newtab-icon-tertiary-color, #d7d7db); +} + +#onboarding-overlay-button-watermark-icon, +#onboarding-overlay-button.onboarding-watermark::after, +#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-icon { + display: none; +} + +#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-watermark-icon { + display: block; +} + +#onboarding-overlay-dialog, +.onboarding-hidden, +#onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out, +#onboarding-tour-sync-page[data-login-state=logged-out] .show-on-logged-in { + display: none; +} + +.onboarding-close-btn { + position: absolute; + top: 15px; + inset-inline-end: 15px; + cursor: pointer; + width: 16px; + height: 16px; + border: none; + background: none; + padding: 0; + } + +.onboarding-close-btn::before { + content: url("chrome://global/skin/icons/close.svg"); + -moz-context-properties: fill, fill-opacity; + fill-opacity: 0; + fill: var(--newtab-icon-primary-color, currentColor); +} + +.onboarding-close-btn:-moz-any(:hover, :active, :focus, :-moz-focusring)::before { + fill-opacity: 0.1; +} + +#onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog { + width: 960px; + height: 510px; + background: #fff; + border: 1px solid rgba(9, 6, 13, 0.2); /* #09060D, 0.2 opacity */ + border-radius: 3px; + position: relative; + margin: 0 calc(50% - 480px); + top: calc(50% - 255px); + display: grid; + grid-template-rows: [dialog-start] 70px [page-start] 1fr [footer-start] 30px [dialog-end]; + grid-template-columns: [dialog-start] 230px [page-start] 1fr [dialog-end]; + box-shadow: 0 3px rgba(0, 0, 0, 0.04); +} + +#onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog:-moz-focusring { + outline: none; +} + +@media (max-height: 510px) { + #onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog { + top: 0; + } +} + +#onboarding-overlay-dialog > header { + grid-row: dialog-start / page-start; + grid-column: dialog-start / tour-end; + margin-top: 22px; + margin-bottom: 0; + margin-inline-end: 0; + margin-inline-start: 36px; + font-size: 22px; + font-weight: 200; +} + +#onboarding-overlay-dialog > nav { + grid-row: dialog-start / footer-start; + grid-column-start: dialog-start; + margin-top: 40px; + margin-bottom: 0; + margin-inline-end: 0; + margin-inline-start: 0; + padding: 0; +} + +#onboarding-overlay-dialog > footer { + grid-column: dialog-start / tour-end; + font-size: 13px; +} + +#onboarding-skip-tour-button { + margin-inline-start: 27px; + margin-bottom: 27px; +} + +/* Onboarding tour list */ +#onboarding-tour-list { + margin: 40px 0 0 0; + padding: 0; + margin-inline-start: 16px; +} + +#onboarding-tour-list .onboarding-tour-item-container { + list-style: none; + outline: none; + position: relative; +} + +#onboarding-tour-list .onboarding-tour-item { + pointer-events: none; + display: list-item; + padding-inline-start: 49px; + padding-top: 14px; + padding-bottom: 14px; + margin-bottom: 9px; + font-size: 16px; + cursor: pointer; + max-height: 54px; + --onboarding-tour-item-active-color: #0A84FF; +} + +#onboarding-tour-list .onboarding-tour-item:dir(rtl) { + background-position-x: right 17px; +} + +#onboarding-tour-list .onboarding-tour-item.onboarding-complete::before { + content: url("img/icons_tour-complete.svg"); + position: relative; + inset-inline-start: 3px; + top: -10px; + float: inline-start; +} + +#onboarding-tour-list .onboarding-tour-item.onboarding-complete { + padding-inline-start: 29px; +} + +#onboarding-tour-list .onboarding-tour-item::after { + content: ""; + display: block; + width: 48px; + height: 48px; + position: absolute; + inset-inline-start: 0px; + top: 0px; + background-color: #3E3D40; + mask-repeat: no-repeat; + mask-position: left 17px top 14px; + mask-size: 20px; +} + +#onboarding-tour-list .onboarding-tour-item:dir(rtl)::after { + mask-position: right 17px top 14px; +} + +#onboarding-tour-list .onboarding-tour-item.onboarding-active::after, +#onboarding-tour-list .onboarding-tour-item-container:hover .onboarding-tour-item::after { + background-color: var(--onboarding-tour-item-active-color); +} + +#onboarding-tour-list .onboarding-tour-item.onboarding-active, +#onboarding-tour-list .onboarding-tour-item-container:hover .onboarding-tour-item { + color: var(--onboarding-tour-item-active-color); + /* With 1px transparent outline, could see a border in the high-constrast mode */ + outline: 1px solid transparent; +} + +/* Default browser tour */ +#onboarding-tour-is-default-browser-msg { + font-size: 16px; + line-height: 21px; + float: inline-end; + margin-inline-end: 26px; + margin-top: -32px; + text-align: center; +} + +/* Sync tour */ +#onboarding-tour-sync-page form { + text-align: center; +} + +#onboarding-tour-sync-page form > h3 { + text-align: center; + margin: 0; + font-size: 22px; + font-weight: normal; +} + +#onboarding-tour-sync-page form > p { + text-align: center; + margin: 3px 0 0 0; + font-size: 15px; + font-weight: normal; +} + +#onboarding-tour-sync-page form > input { + margin-top: 10px; + height: 40px; + width: 80%; + padding: 7px; +} + +#onboarding-tour-sync-page form > #onboarding-tour-sync-button { + padding: 10px 20px; + min-width: 40%; + margin: 15px 0; + float: none; +} + +/* Onboarding tour pages */ +.onboarding-tour-page { + grid-row: page-start / footer-end; + grid-column: page-start; + display: grid; + grid-template-rows: [tour-page-start] 393px [tour-button-start] 1fr [tour-page-end]; + grid-template-columns: [tour-page-start] 368px [tour-content-start] 1fr [tour-page-end]; +} + +.onboarding-tour-description { + grid-row: tour-page-start / tour-page-end; + grid-column: tour-page-start / tour-content-start; + font-size: 15px; + line-height: 22px; + padding-inline-start: 40px; + padding-inline-end: 28px; + max-height: 360px; + overflow: auto; +} + +.onboarding-tour-description > h1 { + font-size: 36px; + margin-top: 16px; + font-weight: 300; + line-height: 44px; +} + +.onboarding-tour-content { + grid-row: tour-page-start / tour-button-start; + grid-column: tour-content-start / tour-page-end; + padding: 0; + text-align: end; +} + +.onboarding-tour-content > img { + width: 352px; + margin: 0; +} + +/* These illustrations need to be stuck on the right side to the border. Thus we + need to flip them horizontally on RTL . */ +.onboarding-tour-content > img:dir(rtl) { + transform: scaleX(-1); +} + +.onboarding-tour-content > iframe { + width: 100%; + height: 100%; + border: none; +} + +.onboarding-tour-button-container { + /* Get higher z-index in order to ensure buttons within container are selectable */ + z-index: 2; + grid-row: tour-button-start / tour-page-end; + grid-column: tour-content-start / tour-page-end; +} + +.onboarding-tour-action-button { + background: #0060df; + /* With 1px transparent border, could see a border in the high-constrast mode */ + border: 1px solid transparent; + border-radius: 2px; + padding: 10px 20px; + font-size: 14px; + font-weight: 600; + line-height: 16px; + color: #fff; + float: inline-end; + margin-inline-end: 26px; + margin-top: -32px; +} + +/* Remove default dotted outline around buttons' text */ +#onboarding-overlay button::-moz-focus-inner, +#onboarding-overlay-button::-moz-focus-inner { + border: 0; +} + +/* Keyboard focus specific outline */ +#onboarding-overlay button:-moz-focusring, +.onboarding-action-button:-moz-focusring, +#onboarding-tour-list .onboarding-tour-item:focus { + outline: 2px solid rgba(0,149,221,0.5); + outline-offset: 1px; + -moz-outline-radius: 2px; +} + +.onboarding-tour-action-button:hover:not([disabled]) { + background: #003eaa; + cursor: pointer; +} + +.onboarding-tour-action-button:active:not([disabled]) { + background: #002275; +} + +.onboarding-tour-action-button:disabled { + opacity: 0.5; +} + +/* Tour Icons */ +#onboarding-tour-singlesearch.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-singlesearch] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_singlesearch.svg"); +} + +#onboarding-tour-private-browsing.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-private-browsing] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_private.svg"); +} + +#onboarding-tour-addons.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-addons] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_addons.svg"); +} + +#onboarding-tour-customize.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-customize] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_customize.svg"); +} + +#onboarding-tour-default-browser.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-default-browser] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_default.svg"); +} + +#onboarding-tour-sync.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-sync] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_sync.svg"); +} + +#onboarding-tour-library.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-library] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_library.svg"); +} + +#onboarding-tour-performance.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-performance] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_performance.svg"); +} + +#onboarding-tour-screenshots.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id=onboarding-tour-screenshots] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_screenshots.svg"); +} + +a#onboarding-tour-screenshots-button, +a#onboarding-tour-screenshots-button:hover, +a#onboarding-tour-screenshots-button:visited { + color: #fff; + text-decoration: none; +} + +/* Tour Notifications */ +#onboarding-notification-bar { + position: fixed; + z-index: 20998; /* We want this always under #onboarding-overlay */ + left: 0; + bottom: 0; + width: 100%; + height: 100px; + min-width: 640px; + background: var(--newtab-snippets-background-color, rgba(255, 255, 255, 0.97)); + border-top: 1px solid var(--newtab-snippets-hairline-color, #e9e9e9); + box-shadow: 0 -1px 4px 0 rgba(12, 12, 13, 0.1); + transition: transform 0.8s; + transform: translateY(122px); +} + +#onboarding-notification-bar.onboarding-opened { + transition: none; + transform: translateY(0px); +} + +#onboarding-notification-close-btn { + position: absolute; + inset-block-start: 50%; + inset-inline-end: 24px; + transform: translateY(-50%); +} + +#onboarding-notification-message-section { + height: 100%; + display: flex; + align-items: center; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +#onboarding-notification-body { + width: 500px; + margin: 0 18px; + color: var(--newtab-text-primary-color, #0c0c0d); + display: inline-block; + max-height: 120px; + overflow: auto; + padding: 15px 0; + box-sizing: border-box; +} + +#onboarding-notification-body * { + font-size: 12px; + font-weight: normal; + margin-top: 5px; +} + +#onboarding-notification-tour-title { + margin: 0; + font-weight: bold; + color: var(--newtab-text-primary-color, #0f1126); + font-size: 14px; +} + +#onboarding-notification-tour-title::before { + content: ""; + background-color: var(--newtab-text-primary-color, #0f1126); + mask-repeat: no-repeat; + mask-size: 14px; + height: 16px; + width: 16px; + float: inline-start; + margin-top: 2px; + margin-inline-end: 2px; +} + +#onboarding-notification-tour-icon { + min-width: 64px; + height: 64px; + background-size: 64px; + background-repeat: no-repeat; + background-image: url("chrome://branding/content/icon64.png"); +} + +.onboarding-action-button { + background: #fbfbfb; + /* With 1px border, could see a border in the high-constrast mode */ + border: 1px solid #c1c1c1; + border-radius: 2px; + padding: 10px 20px; + font-size: 14px; + font-weight: 600; + line-height: 16px; + color: #202340; + min-width: 130px; +} + +.onboarding-action-button:hover { + background-color: #ebebeb; + cursor: pointer; +} + +.onboarding-action-button:active { + background-color: #dadada; +} + +#onboarding-notification-bar .onboarding-action-button { + background-color: var(--newtab-button-secondary-color); + border-color: var(--newtab-border-primary-color); + border-radius: 4px; + color: var(--newtab-text-primary-color); +} + +#onboarding-notification-bar .onboarding-action-button:hover, +#onboarding-notification-bar .onboarding-action-button:active { + background-color: var(--newtab-button-secondary-color); + box-shadow: 0 0 0 5px var(--newtab-card-active-outline-color); + transition: box-shadow 150ms; +} + +@media (min-resolution: 2dppx) { + #onboarding-notification-tour-icon { + background-image: url("chrome://branding/content/icon128.png"); + } +} diff --git a/browser/extensions/onboarding/content/onboarding.js b/browser/extensions/onboarding/content/onboarding.js new file mode 100644 index 0000000000000..fd4275a14072c --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/frame-script */ + +"use strict"; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +ChromeUtils.defineModuleGetter(this, "Onboarding", "resource://onboarding/Onboarding.jsm"); + +const ABOUT_HOME_URL = "about:home"; +const ABOUT_NEWTAB_URL = "about:newtab"; +const ABOUT_WELCOME_URL = "about:welcome"; + +// Load onboarding module only when we enable it. +if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) { + addEventListener("load", function onLoad(evt) { + if (!content || evt.target != content.document) { + return; + } + + let window = evt.target.defaultView; + let location = window.location.href; + if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL || location == ABOUT_WELCOME_URL) { + // We just want to run tests as quickly as possible + // so in the automation test, we don't do `requestIdleCallback`. + if (Cu.isInAutomation) { + new Onboarding(this, window); + return; + } + window.requestIdleCallback(() => { + new Onboarding(this, window); + }); + } + }, true); +} diff --git a/browser/extensions/onboarding/data_events.md b/browser/extensions/onboarding/data_events.md new file mode 100644 index 0000000000000..3fc5ffa41b860 --- /dev/null +++ b/browser/extensions/onboarding/data_events.md @@ -0,0 +1,154 @@ +# Metrics we collect + +We adhere to [Activity Stream's data collection policy](https://github.com/mozilla/activity-stream/blob/master/docs/v2-system-addon/...). Data about your specific browsing behavior or the sites you visit is **never transmitted to any Mozilla server**. At any time, it is easy to **turn off** this data collection by [opting out of Firefox telemetry](https://support.mozilla.org/kb/share-telemetry-data-mozilla-help-improve-fir...). + +## User event pings + +The Onboarding system add-on sends 2 types of pings(HTTPS POST) to the backend [Onyx server](https://github.com/mozilla/onyx) : +- a `session` ping that describes the ending of an Onboarding session (a new tab is closed or refreshed, an overlay is closed, a notification bar is closed), and +- an `event` ping that records specific data about individual user interactions while interacting with Onboarding + +For reference, Onyx is a Mozilla owned service to serve tiles for the current newtab in Firefox. It also receives all the telemetry from the about:newtab and about:home page as well as Activity Stream. It's operated and monitored by the Cloud Services team. + +# Example Onboarding `session` Log + +```js +{ + // These fields are sent from the client + "addon_version": "1.0.0", + "category": ["onboarding-interactions"|"overlay-interactions"|"notification-interactions"], + "client_id": "374dc4d8-0cb2-4ac5-a3cf-c5a9bc3c602e", + "locale": "en-US", + "type": ["onboarding_session" | "overlay_session" | "notification_session"], + "page": ["about:newtab" | "about:home" | "about:welcome"], + "parent_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}", + "root_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}", + "session_begin": 1505440017018, + "session_end": 1505440021992, + "session_id": "{12dasd-213asda-213dkakj}", + "tour_type" ["new" | "update"], + + // These fields are generated on the server + "date": "2016-03-07", + "ip": "10.192.171.13", + "ua": "python-requests/2.9.1", + "receive_at": 1457396660000 +} +``` + +| KEY | DESCRIPTION | | +|-----|-------------|:-----:| +| `addon_version` | [Required] The version of the Onboarding addon. | :one: +| `category` | [Required] Either ["", "overlay-interactions", "notification-interactions"] to identify which kind of the interaction | :one: +| `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.js...) module to provide an identifier for this device. This data is automatically appended by `ping-centre` module | :one: +| `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo-information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two: +| `locale` | The browser chrome's language (e.g. en-US). | :two: +| `page` | [Required] One of ["about:newtab", "about:home", "about:welcome"]| :one: +| `parent_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which parent session. Events happen upon overlay will have the `overlay session uuid` as its `parent_session_id`. Events happen upon notification will have the `notification session uuid` as its `parent_session_id`. | :one: +| `root_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which root session. Every event will have the same `onboarding session uuid` as its `root_session_id` when interact in the same tab. | :one: +| `session_begin` | [Required] Timestamp in (integer) milliseconds when onboarding/overlay/notification becoming visible. | :one: +| `session_end` | [Required] Timestamp in (integer) milliseconds when onboarding/overlay/notification losing focus. | :one: +| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session. We will log different uuid when onboarding is inited/when the overlay is opened/when notification is shown. | :one: +| `tour_type` | [Required] One of ["new", "update"] indicates the user is a `new` user or the `update` user upgrade from the older version | :one: +| `type` | [Required] The type of event. Allowed event strings are defined in the below section | :one: +| `ua` | [Auto populated by Onyx] The user agent string. | :two: +| `ver` | [Auto populated by Onyx] The version of the Onyx API the ping was sent to. | :one: + +# Example Onboarding `event` Log + +```js +{ + "addon_version": "1.0.0", + "bubble_state": ["bubble" | "dot" | "hide"], + "category": ["logo-interactions"|"overlay-interactions"|"notification-interactions"], + "client_id": "374dc4d8-0cb2-4ac5-a3cf-c5a9bc3c602e", + "locale": "en-US", + "logo_state": ["logo" | "watermark"], + "notification_impression": [1-8], + "notification_state": ["show" | "hide" | "finished"], + "page": ["about:newtab" | "about:home" | "about:welcome"], + "parent_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}", + "root_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}", + "current_tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset' + "target_tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset', + "tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset' + "timestamp": 1505440017019, + "tour_type" ["new" | "update"], + "type": ["notification-cta-click" | "overlay-cta-click" | "overlay-nav-click" | "overlay-skip-tour"...], + "width": 950, + + // These fields are generated on the server + "ip": "10.192.171.13", + "ua": "python-requests/2.9.1", + "receive_at": 1457396660000, + "date": "2016-03-07", +} +``` + + +| KEY | DESCRIPTION | | +|-----|-------------|:-----:| +| `addon_version` | [Required] The version of the Onboarding addon. | :one: +| `bubble_state` | [Optional] | One of ["bubble", "dot", "hide"] indicates the current visual state of the speach bubble (content dialog besides the onboarding logo). | :one: +| `category` | [Required] Either ("overlay-interactions", "notification-interactions") to identify which kind of the interaction | :one: +| `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.js...) module to provide an identifier for this device. This data is automatically appended by `ping-centre` module | :one: +| `current_tour_id` | [Optional] id of the current tour. We put "" when this field is not relevant to this event. | :one: +| `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo-information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two: +| `locale` | The browser chrome's language (e.g. en-US). | :two: +| `logo_state` | [Optional] One of ["logo", "watermark"] indicates the overlay is opened while in the default or the watermark state. | :one: +| `notification_impression` | [Optional] An integer to record how many times the current notification tour is shown to the user. Each Notification tour can show not more than 8 times. We put `-1` when this field is not relevant to this event | :one: +| `notification_state` | [Optional] One of ["show", "hide", "finished"] indicates the current notification bar state. | :one: +| `page` | [Required] One of ["about:newtab", "about:home"]| :one: +| `parent_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which parent session. Events happen upon overlay will have the `overlay session uuid` as its `parent_session_id`. Events happen upon notification will have the `notification session uuid` as its `parent_session_id`. | :one: +| `root_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which root session. Every event will have the same `onboarding session uuid` as its `root_session_id` when interact in the same tab. | :one: +| `target_tour_id` | [Optional] id of the target switched tour. We put "" when this field is not relevant to this event. | :one: +| `timestamp` | [Required] Timestamp in (integer) milliseconds when the event triggered | :one: +| `tour_type` | [Required] One of ["new", "update"] indicates the user is a `new` user or the `update` user upgrade from the older version | :one: +| `type` | [Required] The type of event. Allowed event strings are defined in the below section | :one: +| `ua` | [Auto populated by Onyx] The user agent string. | :two: +| `ver` | [Auto populated by Onyx] The version of the Onyx API the ping was sent to. | :one: +| `width` | [Required] Current browser window width rounded by 50 pixels. Collecting rounded values reduces the risk to use these values to derive a unique user identifier. | :one: + +**Where:** + +:one: Firefox data +:two: HTTP protocol data + +## Event types + +Here are all allowed event `type` strings that defined in `OnboardingTelemetry::EVENT_WHITELIST`. + +### Onboarding events + +| EVENT | DESCRIPTION | +|-----------|---------------------| +| `onboarding-logo-click` | event is triggered when a user clicks the logo to open the overlay. | +| `onboarding-register-session` | internal event triggered when loading the onboarding module, will not send out any data. | +| `onboarding-session` | event is sent when the tab unload to track the start and end time of the onboarding session. | +| `onboarding-session-begin` | internal event triggered when the onboarding starts, will not send out any data. | +| `onboarding-session-end` | internal event triggered when the onboarding ends, `onboarding-session` event is the actual event that send to the server. | + +### Overlay events + +| EVENT | DESCRIPTION | +|-----------|---------------------| +| `overlay-close-button-click` | event is triggered when a user clicks close overlay button. | +| `overlay-close-outside-click` | event is triggered when a user clicks outside the overlay area to end the tour. | +| `overlay-cta-click` | event is triggered when a user clicks overlay's Call-To-Action button. | +| `overlay-current-tour` | event is sent when a tour is shown in the overlay. | +| `overlay-nav-click` | event is sent when a user clicks a navigation button in the overlay. | +| `overlay-session` | event is sent when an overlay is closed to track the start and end time of the overlay session. | +| `overlay-session-begin` | internal event triggered when open the overlay, will not send out any data. | +| `overlay-session-end` | internal event is triggered when an overlay session ends. `overlay-session` event is the actual event that send to the server. | +| `overlay-skip-tour` | event is sent when a user clicks `Skip Tour` button in the overlay. | + +### Notification events + +| EVENT | DESCRIPTION | +|-----------|---------------------| +| `notification-appear` | event is sent when a notification appears. | +| `notification-close-button-click` | event is sent when a user clicks close notification button. | +| `notification-cta-click` | event is sent when a user clicks the notification's Call-To-Action button. | +| `notification-session` | event is sent when user closes the notification to track the start and end time of the notification session. | +| `notification-session-begin` | internal event triggered when user open the notification, will not send out any data. | +| `notification-session-end` | internal event is triggered when a notification session ends. `notification-session` event is the actual event that send to the server. | diff --git a/browser/extensions/onboarding/jar.mn b/browser/extensions/onboarding/jar.mn new file mode 100644 index 0000000000000..1d580be9861f9 --- /dev/null +++ b/browser/extensions/onboarding/jar.mn @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[features/onboarding@mozilla.org] chrome.jar: + # resource://onboarding/ is referenced in about:home about:newtab and about:welcome, + # so make it content-accessible. +% resource onboarding %content/ contentaccessible=yes + content/ (content/*) + # Package UITour-lib.js in here rather than under + # /browser/components/uitour to avoid "unreferenced files" error when + # Onboarding extension is not built. + content/lib/UITour-lib.js (/browser/components/uitour/UITour-lib.js) + content/modules/ (*.jsm) diff --git a/browser/extensions/onboarding/locales/en-US/onboarding.properties b/browser/extensions/onboarding/locales/en-US/onboarding.properties new file mode 100644 index 0000000000000..cc545222a107a --- /dev/null +++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties @@ -0,0 +1,126 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# LOCALIZATION NOTE(onboarding.overlay-title2): This string will be used in the overlay title. +onboarding.overlay-title2=Let’s get started +onboarding.skip-tour-button-label=Skip Tour +#LOCALIZATION NOTE(onboarding.button.learnMore): this string is used as a button label, displayed near the message, and shared across all the onboarding notifications. +onboarding.button.learnMore=Learn More +# LOCALIZATION NOTE(onboarding.overlay-icon-tooltip2): This string will be used +# to show the tooltip alongside the notification icon in the overlay tour. %S is +# brandShortName. The tooltip is designed to show in two lines. Please use \n to +# do appropriate line breaking. +onboarding.overlay-icon-tooltip2=New to %S?\nLet’s get started. +# LOCALIZATION NOTE(onboarding.overlay-icon-tooltip-updated2): %S is +# brandShortName. The tooltip is designed to show in two lines. Please use \n to +# do appropriate line breaking. +onboarding.overlay-icon-tooltip-updated2=%S is all new.\nSee what you can do! +# LOCALIZATION NOTE(onboarding.overlay-close-button-tooltip): The overlay close button is an icon button. This tooltip would be shown when mousing hovering on the button. +onboarding.overlay-close-button-tooltip=Close +onboarding.notification-icon-tooltip-updated=See what’s new! +# LOCALIZATION NOTE(onboarding.notification-close-button-tooltip): The notification close button is an icon button. This tooltip would be shown when mousing hovering on the button. +onboarding.notification-close-button-tooltip=Dismiss + +# LOCALIZATION NOTE(onboarding.complete): This string is used to describe an +# onboarding tour item that is complete. +onboarding.complete=Complete + +onboarding.tour-private-browsing=Private Browsing +onboarding.tour-private-browsing.title2=Browse by yourself. +# LOCALIZATION NOTE(onboarding.tour-private-browsing.description3): This string will be used in the private-browsing tour description. %S is brandShortName. +onboarding.tour-private-browsing.description3=Want to keep something to yourself? Use Private Browsing with Tracking Protection. %S will block online trackers while you browse and won’t remember your history after you’ve ended your session. +onboarding.tour-private-browsing.button=Show Private Browsing in Menu +onboarding.notification.onboarding-tour-private-browsing.title=Browse by yourself. +onboarding.notification.onboarding-tour-private-browsing.message2=Want to keep something to yourself? Use Private Browsing with Tracking Protection. + +onboarding.tour-addons=Add-ons +onboarding.tour-addons.title2=Get more done. +# LOCALIZATION NOTE(onboarding.tour-addons.description2): This string will be used in the add-on tour description. %S is brandShortName +onboarding.tour-addons.description2=Add-ons let you add features to %S, so your browser works harder for you. Compare prices, check the weather or express your personality with a custom theme. +onboarding.tour-addons.button=Show Add-ons in Menu +onboarding.notification.onboarding-tour-addons.title=Get more done. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-addons.message): This string will be used in the notification message for the add-ons tour. %S is brandShortName. +onboarding.notification.onboarding-tour-addons.message=Add-ons are small apps you can add to %S that do lots of things — from managing to-do lists, to downloading videos, to changing the look of your browser. + +onboarding.tour-customize=Customize +onboarding.tour-customize.title2=Rearrange your toolbar. +# LOCALIZATION NOTE(onboarding.tour-customize.description2): This string will be used in the customize tour description. %S is brandShortName +onboarding.tour-customize.description2=Put the tools you use most right at your fingertips. Drag, drop, and reorder %S’s toolbar and menu to fit your needs. Or choose a compact theme to make more room for tabbed browsing. +onboarding.tour-customize.button=Show Customize in Menu +onboarding.notification.onboarding-tour-customize.title=Rearrange your toolbar. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-customize.message): This string will be used in the notification message for Customize tour. %S is brandShortName. +onboarding.notification.onboarding-tour-customize.message=Put the tools you use most right at your fingertips. Add more options to your toolbar. Or select a theme to make %S reflect your personality. + +onboarding.tour-default-browser=Default Browser +# LOCALIZATION NOTE(onboarding.tour-default-browser.title2): This string will be used in the default browser tour title. %S is brandShortName +onboarding.tour-default-browser.title2=Make %S your go-to browser. +# LOCALIZATION NOTE(onboarding.tour-default-browser.description2): This string will be used in the default browser tour description. %1$S is brandShortName +onboarding.tour-default-browser.description2=Love %1$S? Set it as your default browser. Open a link from another application, and %1$S will be there for you. +# LOCALIZATION NOTE(onboarding.tour-default-browser.button): Label for a button to open the OS default browser settings where it's not possible to set the default browser directly. (OSX, Linux, Windows 8 and higher) +onboarding.tour-default-browser.button=Open Default Browser Settings +# LOCALIZATION NOTE(onboarding.tour-default-browser.win7.button): Label for a button to directly set the default browser (Windows 7). %S is brandShortName +onboarding.tour-default-browser.win7.button=Make %S Your Default Browser +# LOCALIZATION NOTE(onboarding.tour-default-browser.is-default.message): Label displayed when Firefox is already set as default browser. followed on a new line by "tour-default-browser.is-default.2nd-message". +onboarding.tour-default-browser.is-default.message=You’ve got this! +# LOCALIZATION NOTE(onboarding.tour-default-browser.is-default.2nd-message): Label displayed when Firefox is already set as default browser. %S is brandShortName +onboarding.tour-default-browser.is-default.2nd-message=%S is already your default browser. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-default-browser.title): This string will be used in the notification title for the default browser tour. %S is brandShortName. +onboarding.notification.onboarding-tour-default-browser.title=Make %S your go-to browser. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-default-browser.message): This string will be used in the notification message for the default browser tour. %1$S is brandShortName +onboarding.notification.onboarding-tour-default-browser.message=It doesn’t take much to get the most from %1$S. Just set %1$S as your default browser and put control, customization, and protection on autopilot. + +onboarding.tour-sync2=Sync +onboarding.tour-sync.title2=Pick up where you left off. +onboarding.tour-sync.description2=Sync makes it easy to access bookmarks, passwords, and even open tabs on all your devices. Sync also gives you control of the types of information you want, and don’t want, to share. +onboarding.tour-sync.logged-in.title=You’re signed in to Sync! +# LOCALIZATION NOTE(onboarding.tour-sync.logged-in.description): %1$S is brandShortName. +onboarding.tour-sync.logged-in.description=Sync works when you’re signed in to %1$S on more than one device. Have a mobile device? Install the %1$S app and sign in to get your bookmarks, history, and passwords on the go. +# LOCALIZATION NOTE(onboarding.tour-sync.form.title): This string is displayed +# as a title and followed by onboarding.tour-sync.form.description. +onboarding.tour-sync.form.title=Create a Firefox Account +# LOCALIZATION NOTE(onboarding.tour-sync.form.description): The description +# continues after onboarding.tour-sync.form.title to create a complete sentence. +# If it's not possible for your locale, you can translate this string as +# "Continue to Firefox Sync" instead. +onboarding.tour-sync.form.description=to continue to Firefox Sync +onboarding.tour-sync.button=Next +onboarding.tour-sync.connect-device.button=Connect Another Device +onboarding.tour-sync.email-input.placeholder=Email +onboarding.notification.onboarding-tour-sync.title=Pick up where you left off. +onboarding.notification.onboarding-tour-sync.message=Still sending yourself links to save or read on your phone? Do it the easy way: get Sync and have the things you save here show up on all of your devices. + +onboarding.tour-library=Library +onboarding.tour-library.title=Keep it together. +# LOCALIZATION NOTE (onboarding.tour-library.description2): This string will be used in the library tour description. %1$S is brandShortName +onboarding.tour-library.description2=Check out the new %1$S library in the redesigned toolbar. The library puts the things you’ve seen and saved to %1$S — your browsing history, bookmarks, Pocket list, and synced tabs — in one convenient place. +onboarding.tour-library.button2=Show Library Menu +onboarding.notification.onboarding-tour-library.title=Keep it together. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-library.message): This string will be used in the notification message for the library tour. %S is brandShortName +onboarding.notification.onboarding-tour-library.message=The new %S library puts the great things you’ve discovered on the web in one convenient place. + +onboarding.tour-singlesearch=Address Bar +onboarding.tour-singlesearch.title=Find it faster. +# LOCALIZATION NOTE(onboarding.tour-singlesearch.description): %S is brandShortName +onboarding.tour-singlesearch.description=The address bar might be the most powerful tool in the sleek new %S toolbar. Start typing, and see suggestions based on your browsing and search history. Go to a web address, search the whole web with your default search engine, or send your query directly to a single site with one-click search. +onboarding.tour-singlesearch.button=Show Address Bar +onboarding.notification.onboarding-tour-singlesearch.title=Find it faster. +onboarding.notification.onboarding-tour-singlesearch.message=The unified address bar is the only tool you need to find your way around the web. + +onboarding.tour-performance=Performance +onboarding.tour-performance.title=Browse with the best of ‘em. +# LOCALIZATION NOTE(onboarding.tour-performance.description): %1$S is brandShortName. +onboarding.tour-performance.description=It’s a whole new %1$S, built for faster page loading, smoother scrolling, and more responsive tab switching. These performance upgrades come paired with a modern, intuitive design. Start browsing and experience it for yourself: the best %1$S yet. +onboarding.notification.onboarding-tour-performance.title=Browse with the best of ‘em. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-performance.message): %S is brandShortName. +onboarding.notification.onboarding-tour-performance.message=Prepare yourself for the fastest, smoothest, most reliable %S yet. + +# LOCALIZATION NOTE (onboarding.tour-screenshots): "Screenshots" is the name of the Firefox Screenshots feature and should not be localized. +onboarding.tour-screenshots=Screenshots +onboarding.tour-screenshots.title=Take better screenshots. +# LOCALIZATION NOTE(onboarding.tour-screenshots.description): %S is brandShortName. +onboarding.tour-screenshots.description=Take, save and share screenshots — without leaving %S. Capture a region or an entire page as you browse. Then save to the web for easy access and sharing. +# LOCALIZATION NOTE (onboarding.tour-screenshots.button): "Screenshots" is the name of the Firefox Screenshots feature and should not be localized. +onboarding.tour-screenshots.button=Open Screenshots Website +onboarding.notification.onboarding-tour-screenshots.title=Take better screenshots. +# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-screenshots.message): %S is brandShortName. +onboarding.notification.onboarding-tour-screenshots.message=Take, save and share screenshots — without leaving %S. diff --git a/browser/extensions/moz.build b/browser/extensions/onboarding/locales/jar.mn similarity index 53% copy from browser/extensions/moz.build copy to browser/extensions/onboarding/locales/jar.mn index ab735cf2688f4..0801f85127757 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/onboarding/locales/jar.mn @@ -1,12 +1,8 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: +#filter substitution # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DIRS += [] - -if CONFIG["NIGHTLY_BUILD"]: - DIRS += [ - "translations", - ] +[features/onboarding@mozilla.org] @AB_CD@.jar: +% locale onboarding @AB_CD@ %locale/@AB_CD@/ + locale/@AB_CD@/onboarding.properties (%onboarding.properties) diff --git a/browser/extensions/moz.build b/browser/extensions/onboarding/locales/moz.build similarity index 77% copy from browser/extensions/moz.build copy to browser/extensions/onboarding/locales/moz.build index ab735cf2688f4..d988c0ff9b162 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/onboarding/locales/moz.build @@ -4,9 +4,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-DIRS += [] - -if CONFIG["NIGHTLY_BUILD"]: - DIRS += [ - "translations", - ] +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/extensions/onboarding/manifest.json b/browser/extensions/onboarding/manifest.json new file mode 100644 index 0000000000000..fcf46b444c9b5 --- /dev/null +++ b/browser/extensions/onboarding/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 2, + "name": "Onboarding", + "version": "1.0", + + "applications": { + "gecko": { + "id": "onboarding@mozilla.org" + } + }, + + "background": { + "scripts": ["background.js"] + }, + + "experiment_apis": { + "onboarding": { + "schema": "schema.json", + "parent": { + "scopes": ["addon_parent"], + "script": "api.js", + "events": ["startup"] + } + } + } + } diff --git a/browser/extensions/onboarding/moz.build b/browser/extensions/onboarding/moz.build new file mode 100644 index 0000000000000..4756afe507fbe --- /dev/null +++ b/browser/extensions/onboarding/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Tours") + +DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"] +DEFINES["MOZ_APP_MAXVERSION"] = CONFIG["MOZ_APP_MAXVERSION"] + +DIRS += ["locales"] + +FINAL_TARGET_FILES.features["onboarding@mozilla.org"] += [ + "api.js", + "background.js", + "manifest.json", + "schema.json", +] + +BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"] + +XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"] + +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/extensions/onboarding/schema.json b/browser/extensions/onboarding/schema.json new file mode 100644 index 0000000000000..fe51488c7066f --- /dev/null +++ b/browser/extensions/onboarding/schema.json @@ -0,0 +1 @@ +[] diff --git a/browser/extensions/onboarding/test/browser/.eslintrc.js b/browser/extensions/onboarding/test/browser/.eslintrc.js new file mode 100644 index 0000000000000..9236696e57321 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "plugin:mozilla/browser-test" + ], +}; diff --git a/browser/extensions/onboarding/test/browser/browser.ini b/browser/extensions/onboarding/test/browser/browser.ini new file mode 100644 index 0000000000000..abc2e915d5518 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser.ini @@ -0,0 +1,18 @@ +[DEFAULT] +support-files = + head.js + +[browser_onboarding_accessibility.js] +[browser_onboarding_keyboard.js] +skip-if = debug || os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences +[browser_onboarding_notification.js] +[browser_onboarding_notification_2.js] +[browser_onboarding_notification_3.js] +[browser_onboarding_notification_4.js] +[browser_onboarding_notification_5.js] +[browser_onboarding_notification_click_auto_complete_tour.js] +[browser_onboarding_select_default_tour.js] +[browser_onboarding_skip_tour.js] +[browser_onboarding_tours.js] +[browser_onboarding_tourset.js] +[browser_onboarding_uitour.js] diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js b/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js new file mode 100644 index 0000000000000..37abbd3541d4f --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + "use strict"; + +add_task(async function test_onboarding_overlay_button() { + resetOnboardingDefaultState(); + + info("Wait for onboarding overlay loaded"); + let tab = await openTab(ABOUT_HOME_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + + info("Test accessibility and semantics of the overlay button"); + await ContentTask.spawn(tab.linkedBrowser, {}, function() { + let doc = content.document; + let button = doc.body.firstElementChild; + is(button.id, "onboarding-overlay-button", + "First child is an overlay button"); + ok(button.getAttribute("aria-label"), + "Onboarding button has an accessible label"); + is(button.getAttribute("aria-haspopup"), "true", + "Onboarding button should indicate that it triggers a popup"); + is(button.getAttribute("aria-controls"), "onboarding-overlay-dialog", + "Onboarding button semantically controls an overlay dialog"); + is(button.firstElementChild.getAttribute("role"), "presentation", + "Onboarding button icon should have presentation only semantics"); + }); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_onboarding_notification_bar() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + + info("Test accessibility and semantics of the notification bar"); + await ContentTask.spawn(tab.linkedBrowser, {}, function() { + let doc = content.document; + let footer = doc.getElementById("onboarding-notification-bar"); + + is(footer.getAttribute("aria-labelledby"), doc.getElementById("onboarding-notification-tour-title").id, + "Notification bar should be labelled by the notification tour title text"); + + is(footer.getAttribute("aria-live"), "polite", + "Notification bar should be a live region"); + // Presentational elements + [ + "onboarding-notification-message-section", + "onboarding-notification-tour-icon", + "onboarding-notification-body", + ].forEach(id => + is(doc.getElementById(id).getAttribute("role"), "presentation", + "Element is only used for presentation")); + }); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_onboarding_overlay_dialog() { + resetOnboardingDefaultState(); + + info("Wait for onboarding overlay loaded"); + let tab = await openTab(ABOUT_HOME_URL); + let browser = tab.linkedBrowser; + await promiseOnboardingOverlayLoaded(browser); + + info("Test accessibility and semantics of the dialog overlay"); + await assertModalDialog(browser, { visible: false }); + + info("Click on overlay button and check modal dialog state"); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", + {}, browser); + await promiseOnboardingOverlayOpened(browser); + await assertModalDialog(browser, + { visible: true, focusedId: "onboarding-overlay-dialog" }); + + info("Close the dialog and check modal dialog state"); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", + {}, browser); + await promiseOnboardingOverlayClosed(browser); + await assertModalDialog(browser, { visible: false }); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js b/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js new file mode 100644 index 0000000000000..ab9d2ed6afbc4 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + "use strict"; + +function assertOverlayState(browser, args) { + return ContentTask.spawn(browser, args, ({ tourId, focusedId, visible }) => { + let { document: doc, window} = content; + if (tourId) { + let items = [...doc.querySelectorAll(".onboarding-tour-item")]; + items.forEach(item => is(item.getAttribute("aria-selected"), + item.id === tourId ? "true" : "false", + "Active item should have aria-selected set to true and inactive to false")); + } + if (focusedId) { + let focused = doc.getElementById(focusedId); + is(focused, doc.activeElement, `Focus should be set on ${focusedId}`); + } + if (visible !== undefined) { + let overlay = doc.getElementById("onboarding-overlay"); + is(window.getComputedStyle(overlay).getPropertyValue("display"), + visible ? "block" : "none", + `Onboarding overlay should be ${visible ? "visible" : "invisible"}`); + } + }); +} + +const TOUR_LIST_TEST_DATA = [ + { key: "VK_DOWN", expected: { tourId: TOUR_IDs[1], focusedId: TOUR_IDs[1] }}, + { key: "VK_DOWN", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }}, + { key: "VK_DOWN", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }}, + { key: "VK_DOWN", expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[4] }}, + { key: "VK_UP", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }}, + { key: "VK_UP", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }}, + { key: "VK_TAB", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[3] }}, + { key: "VK_TAB", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[4] }}, + { key: "VK_RETURN", expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[4] }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[3] }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[2] }}, + // VK_SPACE does not work well with EventUtils#synthesizeKey use " " instead + { key: " ", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }}, +]; + +const BUTTONS_TEST_DATA = [ + { key: " ", expected: { focusedId: TOUR_IDs[0], visible: true }}, + { key: "VK_ESCAPE", expected: { focusedId: "onboarding-overlay-button", visible: false }}, + { key: "VK_RETURN", expected: { focusedId: TOUR_IDs[1], visible: true }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: TOUR_IDs[0], visible: true }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }}, + { key: " ", expected: { focusedId: "onboarding-overlay-button", visible: false }}, + { key: "VK_RETURN", expected: { focusedId: TOUR_IDs[1], visible: true }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: TOUR_IDs[0], visible: true }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }}, + { key: "VK_TAB", expected: { focusedId: TOUR_IDs[0], visible: true }}, + { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }}, + { key: "VK_RETURN", expected: { focusedId: "onboarding-overlay-button", visible: false }}, +]; + +add_task(async function test_tour_list_keyboard_navigation() { + resetOnboardingDefaultState(); + + info("Display onboarding overlay on the home page"); + let tab = await openTab(ABOUT_HOME_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", + {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Checking overall overlay tablist semantics"); + await assertOverlaySemantics(tab.linkedBrowser); + + info("Set initial focus on the currently active tab"); + await ContentTask.spawn(tab.linkedBrowser, {}, () => + content.document.querySelector(".onboarding-active").focus()); + await assertOverlayState(tab.linkedBrowser, + { tourId: TOUR_IDs[0], focusedId: TOUR_IDs[0] }); + + for (let { key, options = {}, expected } of TOUR_LIST_TEST_DATA) { + info(`Pressing ${key} to select ${expected.tourId} and have focus on ${expected.focusedId}`); + await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser); + await assertOverlayState(tab.linkedBrowser, expected); + } + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_buttons_keyboard_navigation() { + resetOnboardingDefaultState(); + + info("Wait for onboarding overlay loaded"); + let tab = await openTab(ABOUT_HOME_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + + info("Set keyboard focus on the onboarding overlay button"); + await ContentTask.spawn(tab.linkedBrowser, {}, () => + content.document.getElementById("onboarding-overlay-button").focus()); + await assertOverlayState(tab.linkedBrowser, + { focusedId: "onboarding-overlay-button", visible: false }); + + for (let { key, options = {}, expected } of BUTTONS_TEST_DATA) { + info(`Pressing ${key} to have ${expected.visible ? "visible" : "invisible"} overlay and have focus on ${expected.focusedId}`); + await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser); + await assertOverlayState(tab.linkedBrowser, expected); + } + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_overlay_dialog_keyboard_navigation() { + resetOnboardingDefaultState(); + + info("Wait for onboarding overlay loaded"); + let tab = await openTab(ABOUT_HOME_URL); + let browser = tab.linkedBrowser; + await promiseOnboardingOverlayLoaded(browser); + + info("Test accessibility and semantics of the dialog overlay"); + await assertModalDialog(browser, { visible: false }); + + info("Set keyboard focus on the onboarding overlay button"); + await ContentTask.spawn(browser, {}, () => + content.document.getElementById("onboarding-overlay-button").focus()); + info("Open dialog with keyboard and check the dialog state"); + await BrowserTestUtils.synthesizeKey(" ", {}, browser); + await promiseOnboardingOverlayOpened(browser); + await assertModalDialog(browser, + { visible: true, keyboardFocus: true, focusedId: TOUR_IDs[0] }); + + info("Close the dialog and check modal dialog state"); + await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, browser); + await promiseOnboardingOverlayClosed(browser); + await assertModalDialog(browser, + { visible: false, keyboardFocus: true, focusedId: "onboarding-overlay-button" }); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js new file mode 100644 index 0000000000000..b3e7fc7886817 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(3); + +add_task(async function test_show_tour_notifications_in_order() { + resetOnboardingDefaultState(); + Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1); + skipMuteNotificationOnFirstSession(); + + let tourIds = TOUR_IDs; + let tab = null; + let targetTourId = null; + let expectedPrefUpdates = null; + await loopTourNotificationQueueOnceInOrder(); + await loopTourNotificationQueueOnceInOrder(); + + expectedPrefUpdates = Promise.all([ + promisePrefUpdated("browser.onboarding.notification.finished", true), + promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK), + ]); + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await expectedPrefUpdates; + await assertWatermarkIconDisplayed(tab.linkedBrowser); + let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + ok(!tourId, "Should not prompt each tour for more than 2 chances."); + BrowserTestUtils.removeTab(tab); + + async function loopTourNotificationQueueOnceInOrder() { + for (let i = 0; i < tourIds.length; ++i) { + if (tab) { + await reloadTab(tab); + } else { + tab = await openTab(ABOUT_NEWTAB_URL); + } + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(targetTourId, tourIds[i], "Should show tour notifications in order"); + } + } +}); + +add_task(async function test_open_target_tour_from_notification() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + let { activeNavItemId, activePageId } = await getCurrentActiveTour(tab.linkedBrowser); + + is(targetTourId, activeNavItemId, "Should navigate to the target tour item."); + is(`${targetTourId}-page`, activePageId, "Should display the target tour page."); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js new file mode 100644 index 0000000000000..966102099f504 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(3); + +add_task(async function test_not_show_notification_for_completed_tour() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tourIds = TOUR_IDs; + // Make only the last tour uncompleted + let lastTourId = tourIds[tourIds.length - 1]; + for (let id of tourIds) { + if (id != lastTourId) { + setTourCompletedState(id, true); + } + } + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(targetTourId, lastTourId, "Should not show notification for completed tour"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_skip_notification_for_completed_tour() { + resetOnboardingDefaultState(); + Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1); + skipMuteNotificationOnFirstSession(); + + let tourIds = TOUR_IDs; + // Make only 2nd tour completed + await setTourCompletedState(tourIds[1], true); + + // Test show notification for the 1st tour + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(targetTourId, tourIds[0], "Should show notification for incompleted tour"); + + // Test skip the 2nd tour and show notification for the 3rd tour + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(targetTourId, tourIds[2], "Should skip notification for the completed 2nd tour"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_mute_notification_on_1st_session() { + resetOnboardingDefaultState(); + + // Test no notifications during the mute duration on the 1st session + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + // The tour notification would be prompted on idle, so we wait idle twice here before proceeding + await waitUntilWindowIdle(tab.linkedBrowser); + await waitUntilWindowIdle(tab.linkedBrowser); + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await waitUntilWindowIdle(tab.linkedBrowser); + await waitUntilWindowIdle(tab.linkedBrowser); + let promptCount = Preferences.get("browser.onboarding.notification.prompt-count", 0); + is(0, promptCount, "Should not prompt tour notification during the mute duration on the 1st session"); + + // Test notification prompted after the mute duration on the 1st session + let muteTime = Preferences.get("browser.onboarding.notification.mute-duration-on-first-session-ms"); + let lastTime = Math.floor((Date.now() - muteTime - 1) / 1000); + Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime); + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + promptCount = Preferences.get("browser.onboarding.notification.prompt-count", 0); + is(1, promptCount, "Should prompt tour notification after the mute duration on the 1st session"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js new file mode 100644 index 0000000000000..0010cd90263b6 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(3); + +add_task(async function test_move_on_to_next_notification_when_reaching_max_prompt_count() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + let maxCount = Preferences.get("browser.onboarding.notification.max-prompt-count-per-tour"); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + + let currentTourId = null; + for (let i = maxCount - 1; i > 0; --i) { + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(previousTourId, currentTourId, "Should not move on to next tour notification until reaching the max prompt count per tour"); + } + + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + isnot(previousTourId, currentTourId, "Should move on to next tour notification when reaching the max prompt count per tour"); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_move_on_to_next_notification_when_reaching_max_life_time() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + + let maxTime = Preferences.get("browser.onboarding.notification.max-life-time-per-tour-ms"); + let lastTime = Math.floor((Date.now() - maxTime - 1) / 1000); + Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime); + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + isnot(previousTourId, currentTourId, "Should move on to next tour notification when reaching the max life time per tour"); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_move_on_to_next_notification_after_interacting_with_notification() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser); + + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + isnot(previousTourId, currentTourId, "Should move on to next tour notification after clicking #onboarding-notification-close-btn"); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser); + previousTourId = currentTourId; + + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + isnot(previousTourId, currentTourId, "Should move on to next tour notification after clicking #onboarding-notification-action-btn"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js new file mode 100644 index 0000000000000..57e8ce840f2ad --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(3); + +add_task(async function test_remove_all_tour_notifications_through_close_button() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tourIds = TOUR_IDs; + let tab = null; + let targetTourId = null; + await closeTourNotificationsOneByOne(); + + let expectedPrefUpdates = [ + promisePrefUpdated("browser.onboarding.notification.finished", true), + promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK), + ]; + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await Promise.all(expectedPrefUpdates); + await assertWatermarkIconDisplayed(tab.linkedBrowser); + + let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + ok(!tourId, "Should not prompt tour notifications any more after closing all notifcations."); + BrowserTestUtils.removeTab(tab); + + async function closeTourNotificationsOneByOne() { + for (let i = 0; i < tourIds.length; ++i) { + if (tab) { + await reloadTab(tab); + } else { + tab = await openTab(ABOUT_NEWTAB_URL); + } + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(targetTourId, tourIds[i], `Should show tour notifications of ${targetTourId}`); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser); + await promiseTourNotificationClosed(tab.linkedBrowser); + } + } +}); + +add_task(async function test_remove_all_tour_notifications_through_action_button() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tourIds = TOUR_IDs; + let tab = null; + let targetTourId = null; + await clickTourNotificationActionButtonsOneByOne(); + + let expectedPrefUpdates = [ + promisePrefUpdated("browser.onboarding.notification.finished", true), + promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK), + ]; + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await Promise.all(expectedPrefUpdates); + await assertWatermarkIconDisplayed(tab.linkedBrowser); + + let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations."); + BrowserTestUtils.removeTab(tab); + + async function clickTourNotificationActionButtonsOneByOne() { + for (let i = 0; i < tourIds.length; ++i) { + if (tab) { + await reloadTab(tab); + } else { + tab = await openTab(ABOUT_NEWTAB_URL); + } + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is(targetTourId, tourIds[i], `Should show tour notifications of ${targetTourId}`); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser); + await promiseTourNotificationClosed(tab.linkedBrowser); + } + } +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js new file mode 100644 index 0000000000000..9fd2f25dfe4f4 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_finish_tour_notifcations_after_total_max_life_time() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + + let totalMaxTime = Preferences.get("browser.onboarding.notification.max-life-time-all-tours-ms"); + Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", Math.floor((Date.now() - totalMaxTime) / 1000)); + let expectedPrefUpdates = Promise.all([ + promisePrefUpdated("browser.onboarding.notification.finished", true), + promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK), + ]); + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await expectedPrefUpdates; + await assertWatermarkIconDisplayed(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js new file mode 100644 index 0000000000000..c505b62f7d386 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js @@ -0,0 +1,33 @@ +add_task(async function test_show_click_auto_complete_tour_in_notification() { + resetOnboardingDefaultState(); + skipMuteNotificationOnFirstSession(); + // the second tour is an click-auto-complete tour + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "customize,library"], + ]}); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + // Trigger CTA button to mark the tour as complete + let expectedPrefUpdates = [ + promisePrefUpdated(`browser.onboarding.tour.onboarding-tour-customize.completed`, true), + ]; + BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-customize", {}, tab.linkedBrowser); + BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-customize-button", {}, tab.linkedBrowser); + await Promise.all(expectedPrefUpdates); + + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", {}, gBrowser.selectedBrowser); + let { activeNavItemId } = await getCurrentActiveTour(tab.linkedBrowser); + is("onboarding-tour-customize", activeNavItemId, "the active tour should be the previous shown tour"); + + await reloadTab(tab); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await promiseTourNotificationOpened(tab.linkedBrowser); + let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); + is("onboarding-tour-library", targetTourId, "correctly show the click-autocomplete-tour in notification"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js b/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js new file mode 100644 index 0000000000000..37f345f515faf --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const OVERLAY_ICON_ID = "#onboarding-overlay-button"; +const PRIVATE_BROWSING_TOUR_ID = "#onboarding-tour-private-browsing"; +const ADDONS_TOUR_ID = "#onboarding-tour-addons"; +const CUSTOMIZE_TOUR_ID = "#onboarding-tour-customize"; +const CLASS_ACTIVE = "onboarding-active"; + +add_task(async function test_default_tour_open_the_right_page() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.tour-type", "new"], + ["browser.onboarding.tourset-version", 1], + ["browser.onboarding.seen-tourset-version", 1], + ["browser.onboarding.newtour", "private,addons,customize"], + ]}); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the default tour is active and open the right page"); + let { activeNavItemId, activePageId } = await getCurrentActiveTour(tab.linkedBrowser); + is(`#${activeNavItemId}`, PRIVATE_BROWSING_TOUR_ID, "default tour is active"); + is(activePageId, "onboarding-tour-private-browsing-page", "default tour page is shown"); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_select_first_uncomplete_tour() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.tour-type", "new"], + ["browser.onboarding.tourset-version", 1], + ["browser.onboarding.seen-tourset-version", 1], + ["browser.onboarding.newtour", "private,addons,customize"], + ]}); + setTourCompletedState("onboarding-tour-private-browsing", true); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the first uncomplete tour is selected"); + let { activeNavItemId, activePageId } = await getCurrentActiveTour(tab.linkedBrowser); + is(`#${activeNavItemId}`, ADDONS_TOUR_ID, "default tour is active"); + is(activePageId, "onboarding-tour-addons-page", "default tour page is shown"); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_select_first_tour_when_all_tours_are_complete() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.tour-type", "new"], + ["browser.onboarding.tourset-version", 1], + ["browser.onboarding.seen-tourset-version", 1], + ["browser.onboarding.newtour", "private,addons,customize"], + ]}); + setTourCompletedState("onboarding-tour-private-browsing", true); + setTourCompletedState("onboarding-tour-addons", true); + setTourCompletedState("onboarding-tour-customize", true); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the first tour is selected when all tours are completed"); + let { activeNavItemId, activePageId } = await getCurrentActiveTour(tab.linkedBrowser); + is(`#${activeNavItemId}`, PRIVATE_BROWSING_TOUR_ID, "default tour is active"); + is(activePageId, "onboarding-tour-private-browsing-page", "default tour page is shown"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js new file mode 100644 index 0000000000000..58b2870ce7e66 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + + "use strict"; + +add_task(async function test_skip_onboarding_tours() { + resetOnboardingDefaultState(); + + let tourIds = TOUR_IDs; + let expectedPrefUpdates = [ + promisePrefUpdated("browser.onboarding.notification.finished", true), + promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK), + ]; + tourIds.forEach((id, idx) => expectedPrefUpdates.push(promisePrefUpdated(`browser.onboarding.tour.${id}.completed`, true))); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + let overlayClosedPromise = promiseOnboardingOverlayClosed(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-button", {}, tab.linkedBrowser); + await overlayClosedPromise; + await Promise.all(expectedPrefUpdates); + await assertWatermarkIconDisplayed(tab.linkedBrowser); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_hide_skip_button_via_perf() { + resetOnboardingDefaultState(); + Preferences.set("browser.onboarding.skip-tour-button.hide", true); + + let tab = await openTab(ABOUT_NEWTAB_URL); + let browser = tab.linkedBrowser; + await promiseOnboardingOverlayLoaded(browser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, browser); + await promiseOnboardingOverlayOpened(browser); + + let hasTourButton = await ContentTask.spawn(browser, null, () => { + return content.document.querySelector("#onboarding-skip-tour-button") != null; + }); + + ok(!hasTourButton, "should not render the skip button"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js new file mode 100644 index 0000000000000..612c06ea6e253 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + + "use strict"; + +requestLongerTimeout(2); + +function assertTourCompleted(tourId, expectComplete, browser) { + return ContentTask.spawn(browser, { tourId, expectComplete }, function(args) { + let item = content.document.querySelector(`#${args.tourId}.onboarding-tour-item`); + let completedTextId = `onboarding-complete-${args.tourId}-text`; + let completedText = item.querySelector(`#${completedTextId}`); + if (args.expectComplete) { + ok(item.classList.contains("onboarding-complete"), `Should set the complete #${args.tourId} tour with the complete style`); + ok(completedText, "Text label should be present for a completed item"); + is(completedText.id, completedTextId, "Text label node should have a unique id"); + ok(completedText.getAttribute("aria-label"), "Text label node should have an aria-label attribute set"); + is(item.getAttribute("aria-describedby"), completedTextId, + "Completed item should have aria-describedby attribute set to text label node's id"); + } else { + ok(!item.classList.contains("onboarding-complete"), `Should not set the incomplete #${args.tourId} tour with the complete style`); + ok(!completedText, "Text label should not be present for an incomplete item"); + ok(!item.hasAttribute("aria-describedby"), "Incomplete item should not have aria-describedby attribute set"); + } + }); +} + +add_task(async function test_set_right_tour_completed_style_on_overlay() { + resetOnboardingDefaultState(); + + let tourIds = TOUR_IDs; + // Mark the tours of even number as completed + for (let i = 0; i < tourIds.length; ++i) { + setTourCompletedState(tourIds[i], i % 2 == 0); + } + + let tabs = []; + for (let url of URLs) { + let tab = await openTab(url); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + tabs.push(tab); + } + + for (let i = tabs.length - 1; i >= 0; --i) { + let tab = tabs[i]; + await assertOverlaySemantics(tab.linkedBrowser); + for (let j = 0; j < tourIds.length; ++j) { + await assertTourCompleted(tourIds[j], j % 2 == 0, tab.linkedBrowser); + } + BrowserTestUtils.removeTab(tab); + } +}); + +add_task(async function test_click_action_button_to_set_tour_completed() { + resetOnboardingDefaultState(); + const CUSTOM_TOUR_IDs = [ + "onboarding-tour-private-browsing", + "onboarding-tour-addons", + "onboarding-tour-customize", + ]; + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "private,addons,customize"], + ]}); + + let tourIds = CUSTOM_TOUR_IDs; + let tabs = []; + for (let url of URLs) { + let tab = await openTab(url); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + tabs.push(tab); + } + + let completedTourId = tourIds[0]; + let expectedPrefUpdate = promisePrefUpdated(`browser.onboarding.tour.${completedTourId}.completed`, true); + await BrowserTestUtils.synthesizeMouseAtCenter(`#${completedTourId}-page .onboarding-tour-action-button`, {}, gBrowser.selectedBrowser); + await expectedPrefUpdate; + + for (let i = tabs.length - 1; i >= 0; --i) { + let tab = tabs[i]; + await assertOverlaySemantics(tab.linkedBrowser); + for (let id of tourIds) { + await assertTourCompleted(id, id == completedTourId, tab.linkedBrowser); + } + BrowserTestUtils.removeTab(tab); + } +}); + +add_task(async function test_set_watermark_after_all_tour_completed() { + resetOnboardingDefaultState(); + + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.tour-type", "new"], + ]}); + + let tabs = []; + for (let url of URLs) { + let tab = await openTab(url); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + tabs.push(tab); + } + let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK); + TOUR_IDs.forEach(id => Preferences.set(`browser.onboarding.tour.${id}.completed`, true)); + await expectedPrefUpdate; + + for (let tab of tabs) { + await assertWatermarkIconDisplayed(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js b/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js new file mode 100644 index 0000000000000..3bbc50b69083f --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(2); + +async function testTourIDs(browser, tourIDs) { + await ContentTask.spawn(browser, tourIDs, async (tourIDsContent) => { + let doc = content.document; + let doms = doc.querySelectorAll(".onboarding-tour-item"); + Assert.equal(doms.length, tourIDsContent.length, "has exact tour numbers"); + doms.forEach((dom, idx) => { + Assert.equal(tourIDsContent[idx], dom.id, "contain defined onboarding id"); + }); + }); +} + +add_task(async function test_onboarding_default_new_tourset() { + resetOnboardingDefaultState(); + + let tab = await openTab(ABOUT_NEWTAB_URL); + let browser = tab.linkedBrowser; + await promiseOnboardingOverlayLoaded(browser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, browser); + await promiseOnboardingOverlayOpened(browser); + + await testTourIDs(browser, TOUR_IDs); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_onboarding_custom_new_tourset() { + const CUSTOM_NEW_TOURs = [ + "onboarding-tour-private-browsing", + "onboarding-tour-addons", + "onboarding-tour-customize", + ]; + + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.tour-type", "new"], + ["browser.onboarding.tourset-version", 1], + ["browser.onboarding.seen-tourset-version", 1], + ["browser.onboarding.newtour", "private,addons,customize"], + ]}); + + let tab = await openTab(ABOUT_NEWTAB_URL); + let browser = tab.linkedBrowser; + await promiseOnboardingOverlayLoaded(browser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, browser); + await promiseOnboardingOverlayOpened(browser); + + await testTourIDs(browser, CUSTOM_NEW_TOURs); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_onboarding_custom_update_tourset() { + const CUSTOM_UPDATE_TOURs = [ + "onboarding-tour-customize", + "onboarding-tour-private-browsing", + "onboarding-tour-addons", + ]; + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.tour-type", "update"], + ["browser.onboarding.tourset-version", 1], + ["browser.onboarding.seen-tourset-version", 1], + ["browser.onboarding.updatetour", "customize,private,addons"], + ]}); + + let tab = await openTab(ABOUT_NEWTAB_URL); + let browser = tab.linkedBrowser; + await promiseOnboardingOverlayLoaded(browser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, browser); + await promiseOnboardingOverlayOpened(browser); + + await testTourIDs(browser, CUSTOM_UPDATE_TOURs); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js b/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js new file mode 100644 index 0000000000000..716dd59651e4a --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js @@ -0,0 +1,167 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(3); + +function promisePopupChange(popup, expectedState) { + return new Promise(resolve => { + let event = expectedState == "open" ? "popupshown" : "popuphidden"; + popup.addEventListener(event, resolve, { once: true }); + }); +} + +async function promiseOpenOnboardingOverlay(tab) { + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser); + return promiseOnboardingOverlayOpened(tab.linkedBrowser); +} + +async function triggerUITourHighlight(tourName, tab) { + await promiseOpenOnboardingOverlay(tab); + BrowserTestUtils.synthesizeMouseAtCenter(`#onboarding-tour-${tourName}`, {}, tab.linkedBrowser); + BrowserTestUtils.synthesizeMouseAtCenter(`#onboarding-tour-${tourName}-button`, {}, tab.linkedBrowser); +} + +add_task(async function test_clean_up_uitour_after_closing_overlay() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "library"], + ]}); + + // Trigger UITour showHighlight + let highlight = document.getElementById("UITourHighlightContainer"); + let highlightOpenPromise = promisePopupChange(highlight, "open"); + let tab = await openTab(ABOUT_NEWTAB_URL); + await triggerUITourHighlight("library", tab); + await highlightOpenPromise; + is(highlight.state, "open", "Should show UITour highlight"); + is(highlight.getAttribute("targetName"), "library", "UITour should highlight library"); + + // Close the overlay by clicking the overlay + let highlightClosePromise = promisePopupChange(highlight, "closed"); + BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser); + await promiseOnboardingOverlayClosed(tab.linkedBrowser); + await highlightClosePromise; + is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay"); + + // Trigger UITour showHighlight again + highlightOpenPromise = promisePopupChange(highlight, "open"); + await triggerUITourHighlight("library", tab); + await highlightOpenPromise; + is(highlight.state, "open", "Should show UITour highlight"); + is(highlight.getAttribute("targetName"), "library", "UITour should highlight library"); + + // Close the overlay by clicking the skip-tour button + highlightClosePromise = promisePopupChange(highlight, "closed"); + BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-btn", {}, tab.linkedBrowser); + await promiseOnboardingOverlayClosed(tab.linkedBrowser); + await highlightClosePromise; + is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the skip-tour button"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_clean_up_uitour_after_navigating_to_other_tour_by_keyboard() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "singlesearch,customize"], + ]}); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOpenOnboardingOverlay(tab); + + // Navigate to the Customize tour to trigger UITour showHighlight + let highlight = document.getElementById("UITourHighlightContainer"); + let highlightOpenPromise = promisePopupChange(highlight, "open"); + tab.linkedBrowser.focus(); // Make sure the key event will be fired on the focused page + await BrowserTestUtils.synthesizeKey("VK_TAB", {}, tab.linkedBrowser); + await BrowserTestUtils.synthesizeKey("VK_TAB", {}, tab.linkedBrowser); + await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser); + await BrowserTestUtils.synthesizeKey("VK_TAB", {}, tab.linkedBrowser); + await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser); + await highlightOpenPromise; + is(highlight.state, "open", "Should show UITour highlight"); + is(highlight.getAttribute("targetName"), "customize", "UITour should highlight customize"); + + // Navigate to the Single-Search tour + let highlightClosePromise = promisePopupChange(highlight, "closed"); + tab.linkedBrowser.focus(); // Make sure the key event will be fired on the focused page + await BrowserTestUtils.synthesizeKey("VK_TAB", { shiftKey: true }, tab.linkedBrowser); + await BrowserTestUtils.synthesizeKey("VK_TAB", { shiftKey: true }, tab.linkedBrowser); + await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser); + await highlightClosePromise; + is(highlight.state, "closed", "Should close UITour highlight after navigating to another tour by keyboard"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_clean_up_uitour_after_navigating_to_other_tour_by_mouse() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "singlesearch,customize"], + ]}); + + // Navigate to the Customize tour to trigger UITour showHighlight + let highlight = document.getElementById("UITourHighlightContainer"); + let highlightOpenPromise = promisePopupChange(highlight, "open"); + let tab = await openTab(ABOUT_NEWTAB_URL); + await triggerUITourHighlight("customize", tab); + await highlightOpenPromise; + is(highlight.state, "open", "Should show UITour highlight"); + is(highlight.getAttribute("targetName"), "customize", "UITour should highlight customize"); + + // Navigate to the Single-Search tour + let highlightClosePromise = promisePopupChange(highlight, "closed"); + BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-singlesearch", {}, tab.linkedBrowser); + await highlightClosePromise; + is(highlight.state, "closed", "Should close UITour highlight after navigating to another tour by mouse"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_clean_up_uitour_on_page_unload() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "singlesearch,customize"], + ]}); + + // Trigger UITour showHighlight + let highlight = document.getElementById("UITourHighlightContainer"); + let highlightOpenPromise = promisePopupChange(highlight, "open"); + let tab = await openTab(ABOUT_NEWTAB_URL); + await triggerUITourHighlight("customize", tab); + await highlightOpenPromise; + is(highlight.state, "open", "Should show UITour highlight"); + is(highlight.getAttribute("targetName"), "customize", "UITour should highlight customize"); + + // Load another page to unload the current page + let highlightClosePromise = promisePopupChange(highlight, "closed"); + await BrowserTestUtils.loadURI(tab.linkedBrowser, "http://example.com"); + await highlightClosePromise; + is(highlight.state, "closed", "Should close UITour highlight after page unloaded"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_clean_up_uitour_on_window_resize() { + resetOnboardingDefaultState(); + await SpecialPowers.pushPrefEnv({set: [ + ["browser.onboarding.newtour", "singlesearch,customize"], + ]}); + + // Trigger UITour showHighlight + let highlight = document.getElementById("UITourHighlightContainer"); + let highlightOpenPromise = promisePopupChange(highlight, "open"); + let tab = await openTab(ABOUT_NEWTAB_URL); + await triggerUITourHighlight("customize", tab); + await highlightOpenPromise; + is(highlight.state, "open", "Should show UITour highlight"); + is(highlight.getAttribute("targetName"), "customize", "UITour should highlight customize"); + + // Resize window to destroy the onboarding tour + const originalWidth = window.innerWidth; + let highlightClosePromise = promisePopupChange(highlight, "closed"); + window.innerWidth = 300; + await highlightClosePromise; + is(highlight.state, "closed", "Should close UITour highlight after window resized"); + window.innerWidth = originalWidth; + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/onboarding/test/browser/head.js b/browser/extensions/onboarding/test/browser/head.js new file mode 100644 index 0000000000000..9a66f60df88a7 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/head.js @@ -0,0 +1,288 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let { Preferences } = ChromeUtils.import("resource://gre/modules/Preferences.jsm", {}); + +const ABOUT_HOME_URL = "about:home"; +const ABOUT_NEWTAB_URL = "about:newtab"; +const URLs = [ABOUT_HOME_URL, ABOUT_NEWTAB_URL]; +const TOUR_IDs = [ + "onboarding-tour-performance", + "onboarding-tour-private-browsing", + "onboarding-tour-screenshots", + "onboarding-tour-addons", + "onboarding-tour-customize", + "onboarding-tour-default-browser", +]; +const UPDATE_TOUR_IDs = [ + "onboarding-tour-performance", + "onboarding-tour-library", + "onboarding-tour-screenshots", + "onboarding-tour-singlesearch", + "onboarding-tour-customize", + "onboarding-tour-sync", +]; +const ICON_STATE_WATERMARK = "watermark"; +const ICON_STATE_DEFAULT = "default"; + +registerCleanupFunction(resetOnboardingDefaultState); + +function resetOnboardingDefaultState() { + // All the prefs should be reset to the default states + // and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here. + Preferences.set("browser.onboarding.enabled", true); + Preferences.set("browser.onboarding.state", ICON_STATE_DEFAULT); + Preferences.set("browser.onboarding.notification.finished", false); + Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000); + Preferences.set("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000); + Preferences.set("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000); + Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 8); + Preferences.reset("browser.onboarding.notification.last-time-of-changing-tour-sec"); + Preferences.reset("browser.onboarding.notification.prompt-count"); + Preferences.reset("browser.onboarding.notification.tour-ids-queue"); + Preferences.reset("browser.onboarding.skip-tour-button.hide"); + TOUR_IDs.forEach(id => Preferences.reset(`browser.onboarding.tour.${id}.completed`)); + UPDATE_TOUR_IDs.forEach(id => Preferences.reset(`browser.onboarding.tour.${id}.completed`)); +} + +function setTourCompletedState(tourId, state) { + Preferences.set(`browser.onboarding.tour.${tourId}.completed`, state); +} + +async function openTab(url) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + let loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.loadURI(tab.linkedBrowser, url); + await loadedPromise; + return tab; +} + +function reloadTab(tab) { + let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + tab.linkedBrowser.reload(); + return reloadPromise; +} + +function promiseOnboardingOverlayLoaded(browser) { + function isLoaded() { + let doc = content && content.document; + if (doc.querySelector("#onboarding-overlay")) { + ok(true, "Should load onboarding overlay"); + return Promise.resolve(); + } + return new Promise(resolve => { + let observer = new content.MutationObserver(mutations => { + mutations.forEach(mutation => { + let overlay = Array.from(mutation.addedNodes) + .find(node => node.id == "onboarding-overlay"); + if (overlay) { + observer.disconnect(); + ok(true, "Should load onboarding overlay"); + resolve(); + } + }); + }); + observer.observe(doc.body, { childList: true }); + }); + } + return ContentTask.spawn(browser, {}, isLoaded); +} + +function promiseOnboardingOverlayOpened(browser) { + return BrowserTestUtils.waitForCondition(() => + ContentTask.spawn(browser, {}, () => + content.document.querySelector("#onboarding-overlay").classList.contains( + "onboarding-opened")), + "Should open onboarding overlay", + 100, + 30 + ); +} + +function promiseOnboardingOverlayClosed(browser) { + return BrowserTestUtils.waitForCondition(() => + ContentTask.spawn(browser, {}, () => + !content.document.querySelector("#onboarding-overlay").classList.contains( + "onboarding-opened")), + "Should close onboarding overlay", + 100, + 30 + ); +} + +function promisePrefUpdated(name, expectedValue) { + return new Promise(resolve => { + let onUpdate = actualValue => { + Preferences.ignore(name, onUpdate); + is(expectedValue, actualValue, `Should update the pref of ${name}`); + resolve(); + }; + Preferences.observe(name, onUpdate); + }); +} + +function promiseTourNotificationOpened(browser) { + function isOpened() { + let doc = content && content.document; + let notification = doc.querySelector("#onboarding-notification-bar"); + if (notification && notification.classList.contains("onboarding-opened")) { + ok(true, "Should open tour notification"); + return Promise.resolve(); + } + return new Promise(resolve => { + let observer = new content.MutationObserver(mutations => { + mutations.forEach(mutation => { + let bar = Array.from(mutation.addedNodes) + .find(node => node.id == "onboarding-notification-bar"); + if (bar && bar.classList.contains("onboarding-opened")) { + observer.disconnect(); + ok(true, "Should open tour notification"); + resolve(); + } + }); + }); + observer.observe(doc.body, { childList: true }); + }); + } + return ContentTask.spawn(browser, {}, isOpened); +} + +function promiseTourNotificationClosed(browser) { + let condition = () => { + return ContentTask.spawn(browser, {}, function() { + return new Promise(resolve => { + let bar = content.document.querySelector("#onboarding-notification-bar"); + if (bar && !bar.classList.contains("onboarding-opened")) { + resolve(true); + return; + } + resolve(false); + }); + }); + }; + return BrowserTestUtils.waitForCondition( + condition, + "Should close tour notification", + 100, + 30 + ); +} + +function getCurrentNotificationTargetTourId(browser) { + return ContentTask.spawn(browser, {}, function() { + let bar = content.document.querySelector("#onboarding-notification-bar"); + return bar ? bar.dataset.targetTourId : null; + }); +} + +function getCurrentActiveTour(browser) { + return ContentTask.spawn(browser, {}, function() { + let list = content.document.querySelector("#onboarding-tour-list"); + let items = list.querySelectorAll(".onboarding-tour-item"); + let activeNavItemId = null; + for (let item of items) { + if (item.classList.contains("onboarding-active")) { + if (!activeNavItemId) { + activeNavItemId = item.id; + } else { + ok(false, "There are more than one item marked as active."); + } + } + } + let activePageId = null; + let pages = content.document.querySelectorAll(".onboarding-tour-page"); + for (let page of pages) { + if (page.style.display != "none") { + if (!activePageId) { + activePageId = page.id; + } else { + ok(false, "Thre are more than one tour page visible."); + } + } + } + return { activeNavItemId, activePageId }; + }); +} + +function waitUntilWindowIdle(browser) { + return ContentTask.spawn(browser, {}, function() { + return new Promise(resolve => content.requestIdleCallback(resolve)); + }); +} + +function skipMuteNotificationOnFirstSession() { + Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 0); +} + +function assertOverlaySemantics(browser) { + return ContentTask.spawn(browser, {}, function() { + let doc = content.document; + + info("Checking dialog"); + let dialog = doc.getElementById("onboarding-overlay-dialog"); + is(dialog.getAttribute("role"), "dialog", + "Dialog should have a dialog role attribute set"); + is(dialog.tabIndex, "-1", "Dialog should be focusable but not in tab order"); + is(dialog.getAttribute("aria-labelledby"), "onboarding-header", + "Dialog should be labaled by its header"); + + info("Checking the tablist container"); + is(doc.getElementById("onboarding-tour-list").getAttribute("role"), "tablist", + "Tour list should have a tablist role attribute set"); + + info("Checking each tour item that represents the tab"); + let items = [...doc.querySelectorAll(".onboarding-tour-item")]; + items.forEach(item => { + is(item.parentNode.getAttribute("role"), "presentation", + "Parent should have no semantic value"); + is(item.getAttribute("aria-selected"), + item.classList.contains("onboarding-active") ? "true" : "false", + "Active item should have aria-selected set to true and inactive to false"); + is(item.tabIndex, "0", "Item tab index must be set for keyboard accessibility"); + is(item.getAttribute("role"), "tab", "Item should have a tab role attribute set"); + let tourPanelId = `${item.id}-page`; + is(item.getAttribute("aria-controls"), tourPanelId, + "Item should have aria-controls attribute point to its tabpanel"); + let panel = doc.getElementById(tourPanelId); + is(panel.getAttribute("role"), "tabpanel", + "Tour panel should have a tabpanel role attribute set"); + is(panel.getAttribute("aria-labelledby"), item.id, + "Tour panel should have aria-labelledby attribute point to its tab"); + }); + }); +} + +function assertModalDialog(browser, args) { + return ContentTask.spawn(browser, args, ({ keyboardFocus, visible, focusedId }) => { + let doc = content.document; + let overlayButton = doc.getElementById("onboarding-overlay-button"); + if (visible) { + [...doc.body.children].forEach(child => + child.id !== "onboarding-overlay" && + is(child.getAttribute("aria-hidden"), "true", + "Content should not be visible to screen reader")); + is(focusedId ? doc.getElementById(focusedId) : doc.body, + doc.activeElement, `Focus should be on ${focusedId || "body"}`); + is(keyboardFocus ? "true" : undefined, + overlayButton.dataset.keyboardFocus, + "Overlay button focus state is saved correctly"); + } else { + [...doc.body.children].forEach( + child => ok(!child.hasAttribute("aria-hidden"), + "Content should be visible to screen reader")); + if (keyboardFocus) { + is(overlayButton, doc.activeElement, + "Focus should be set on overlay button"); + } + ok(!overlayButton.dataset.keyboardFocus, + "Overlay button focus state should be cleared"); + } + }); +} + +function assertWatermarkIconDisplayed(browser) { + return ContentTask.spawn(browser, {}, function() { + let overlayButton = content.document.getElementById("onboarding-overlay-button"); + ok(overlayButton.classList.contains("onboarding-watermark"), "Should display the watermark onboarding icon"); + }); +} diff --git a/browser/extensions/onboarding/test/unit/.eslintrc.js b/browser/extensions/onboarding/test/unit/.eslintrc.js new file mode 100644 index 0000000000000..58f8bd73ee487 --- /dev/null +++ b/browser/extensions/onboarding/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "plugin:mozilla/xpcshell-test", + ], +}; diff --git a/browser/extensions/onboarding/test/unit/head.js b/browser/extensions/onboarding/test/unit/head.js new file mode 100644 index 0000000000000..715ba8589b4e1 --- /dev/null +++ b/browser/extensions/onboarding/test/unit/head.js @@ -0,0 +1,54 @@ +/** + * Provides infrastructure for automated onboarding components tests. + */ + +"use strict"; + +/* global Cc, Ci, Cu */ +ChromeUtils.import("resource://gre/modules/Preferences.jsm"); +ChromeUtils.import("resource://gre/modules/Services.jsm"); +ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "resProto", + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler"); + +// Load our bootstrap extension manifest so we can access our chrome/resource URIs. +// Cargo culted from formautofill system add-on +const EXTENSION_ID = "onboarding@mozilla.org"; +let extensionDir = Services.dirsvc.get("GreD", Ci.nsIFile); +extensionDir.append("browser"); +extensionDir.append("features"); +extensionDir.append(EXTENSION_ID); +let resourceURI; +// If the unpacked extension doesn't exist, use the packed version. +if (!extensionDir.exists()) { + extensionDir.leafName += ".xpi"; + + resourceURI = "jar:" + Services.io.newFileURI(extensionDir).spec + "!/chrome/content/"; +} else { + resourceURI = Services.io.newFileURI(extensionDir).spec + "/chrome/content/"; +} +Components.manager.addBootstrappedManifestLocation(extensionDir); + +resProto.setSubstitution("onboarding", Services.io.newURI(resourceURI)); + +const TOURSET_VERSION = 1; +const NEXT_TOURSET_VERSION = 2; +const PREF_TOUR_TYPE = "browser.onboarding.tour-type"; +const PREF_TOURSET_VERSION = "browser.onboarding.tourset-version"; +const PREF_SEEN_TOURSET_VERSION = "browser.onboarding.seen-tourset-version"; + +function resetOnboardingDefaultState() { + // All the prefs should be reset to what prefs should looks like in a new user profile + Services.prefs.setIntPref(PREF_TOURSET_VERSION, TOURSET_VERSION); + Services.prefs.clearUserPref(PREF_SEEN_TOURSET_VERSION); + Services.prefs.clearUserPref(PREF_TOUR_TYPE); +} + +function resetOldProfileDefaultState() { + // All the prefs should be reset to what prefs should looks like in a older new user profile + Services.prefs.setIntPref(PREF_TOURSET_VERSION, TOURSET_VERSION); + Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, 0); + Services.prefs.clearUserPref(PREF_TOUR_TYPE); +} diff --git a/browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js b/browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js new file mode 100644 index 0000000000000..489ae6eebce17 --- /dev/null +++ b/browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js @@ -0,0 +1,89 @@ +/* + * Test for onboarding tour type check. + */ + +"use strict"; + +ChromeUtils.import("resource://onboarding/modules/OnboardingTourType.jsm"); + +add_task(async function() { + info("Starting testcase: When New user open the browser first time"); + resetOnboardingDefaultState(); + OnboardingTourType.check(); + + Assert.equal(Preferences.get(PREF_TOUR_TYPE), "new", "should show the new user tour"); + Assert.equal(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION, + "tourset version should not change"); + Assert.equal(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION, + "seen tourset version should be set as the tourset version"); +}); + +add_task(async function() { + info("Starting testcase: When New user restart the browser"); + resetOnboardingDefaultState(); + Preferences.set(PREF_TOUR_TYPE, "new"); + Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION); + OnboardingTourType.check(); + + Assert.equal(Preferences.get(PREF_TOUR_TYPE), "new", "should show the new user tour"); + Assert.equal(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION, + "tourset version should not change"); + Assert.equal(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION, + "seen tourset version should be set as the tourset version"); +}); + +add_task(async function() { + info("Starting testcase: When New User choosed to hide the overlay and restart the browser"); + resetOnboardingDefaultState(); + Preferences.set(PREF_TOUR_TYPE, "new"); + Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION); + OnboardingTourType.check(); + + Assert.equal(Preferences.get(PREF_TOUR_TYPE), "new", "should show the new user tour"); + Assert.equal(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION, + "tourset version should not change"); + Assert.equal(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION, + "seen tourset version should be set as the tourset version"); +}); + +add_task(async function() { + info("Starting testcase: When New User updated to the next major version and restart the browser"); + resetOnboardingDefaultState(); + Preferences.set(PREF_TOURSET_VERSION, NEXT_TOURSET_VERSION); + Preferences.set(PREF_TOUR_TYPE, "new"); + Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION); + OnboardingTourType.check(); + + Assert.equal(Preferences.get(PREF_TOUR_TYPE), "update", "should show the update user tour"); + Assert.equal(Preferences.get(PREF_TOURSET_VERSION), NEXT_TOURSET_VERSION, + "tourset version should not change"); + Assert.equal(Preferences.get(PREF_SEEN_TOURSET_VERSION), NEXT_TOURSET_VERSION, + "seen tourset version should be set as the tourset version"); +}); + +add_task(async function() { + info("Starting testcase: When New User prefer hide the tour, then updated to the next major version and restart the browser"); + resetOnboardingDefaultState(); + Preferences.set(PREF_TOURSET_VERSION, NEXT_TOURSET_VERSION); + Preferences.set(PREF_TOUR_TYPE, "new"); + Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION); + OnboardingTourType.check(); + + Assert.equal(Preferences.get(PREF_TOUR_TYPE), "update", "should show the update user tour"); + Assert.equal(Preferences.get(PREF_TOURSET_VERSION), NEXT_TOURSET_VERSION, + "tourset version should not change"); + Assert.equal(Preferences.get(PREF_SEEN_TOURSET_VERSION), NEXT_TOURSET_VERSION, + "seen tourset version should be set as the tourset version"); +}); + +add_task(async function() { + info("Starting testcase: When User update from browser version < 56"); + resetOldProfileDefaultState(); + OnboardingTourType.check(); + + Assert.equal(Preferences.get(PREF_TOUR_TYPE), "update", "should show the update user tour"); + Assert.equal(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION, + "tourset version should not change"); + Assert.equal(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION, + "seen tourset version should be set as the tourset version"); +}); diff --git a/browser/extensions/onboarding/test/unit/xpcshell.ini b/browser/extensions/onboarding/test/unit/xpcshell.ini new file mode 100644 index 0000000000000..ed484d0f200f2 --- /dev/null +++ b/browser/extensions/onboarding/test/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +firefox-appdir = browser +head = head.js + +[test-onboarding-tour-type.js] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index da8cfeca1a649..539cd6419e673 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -250,6 +250,7 @@ @RESPATH@/browser/chrome/icons/default/default64.png @RESPATH@/browser/chrome/icons/default/default128.png #endif +@RESPATH@/browser/features/*
; [DevTools Startup Files] @RESPATH@/browser/chrome/devtools-startup@JAREXT@ diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index 0946188813da5..5a91aa599a1e6 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -58,6 +58,7 @@ l10n-%: @$(MAKE) -C ../../toolkit/locales l10n-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)' @$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$* @$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$* + @$(MAKE) -C ../extensions/onboarding/locales AB_CD=$* XPI_NAME=locale-$* @$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)' @$(MAKE) -C ../../devtools/startup/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)' @$(MAKE) l10n AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR) @@ -71,6 +72,7 @@ chrome-%: @$(MAKE) -C ../../toolkit/locales chrome-$* @$(MAKE) -C ../../services/sync/locales chrome AB_CD=$* @$(MAKE) -C ../../extensions/spellcheck/locales chrome AB_CD=$* + @$(MAKE) -C ../extensions/onboarding/locales chrome AB_CD=$* @$(MAKE) -C ../../devtools/client/locales chrome AB_CD=$* @$(MAKE) -C ../../devtools/startup/locales chrome AB_CD=$* @$(MAKE) chrome AB_CD=$* diff --git a/browser/locales/filter.py b/browser/locales/filter.py index 562465d06e6c0..3178e7a058757 100644 --- a/browser/locales/filter.py +++ b/browser/locales/filter.py @@ -19,6 +19,7 @@ def test(mod, path, entity=None): "devtools/startup", "browser", "browser/extensions/formautofill", + "browser/extensions/onboarding", "browser/extensions/report-site-issue", "extensions/spellcheck", "other-licenses/branding/firefox", diff --git a/browser/locales/l10n.ini b/browser/locales/l10n.ini index 7a6599740b205..c70485a63d53c 100644 --- a/browser/locales/l10n.ini +++ b/browser/locales/l10n.ini @@ -13,6 +13,7 @@ dirs = browser devtools/client devtools/startup browser/extensions/formautofill + browser/extensions/onboarding browser/extensions/report-site-issue
[includes] diff --git a/browser/locales/l10n.toml b/browser/locales/l10n.toml index 130de78b46020..67f7b08f28a07 100644 --- a/browser/locales/l10n.toml +++ b/browser/locales/l10n.toml @@ -131,6 +131,10 @@ locales = [ reference = "browser/extensions/formautofill/locales/en-US/**" l10n = "{l}browser/extensions/formautofill/**"
+[[paths]] + reference = "browser/extensions/onboarding/locales/en-US/**" + l10n = "{l}browser/extensions/onboarding/**" + [[paths]] reference = "browser/extensions/report-site-issue/locales/en-US/**" l10n = "{l}browser/extensions/report-site-issue/**" diff --git a/extensions/permissions/PermissionManager.cpp b/extensions/permissions/PermissionManager.cpp index 90b4278840308..eb3607c21b98e 100644 --- a/extensions/permissions/PermissionManager.cpp +++ b/extensions/permissions/PermissionManager.cpp @@ -126,7 +126,11 @@ static const nsLiteralCString kPreloadPermissions[] = { // interception when a user has disabled storage for a specific site. Once // service worker interception moves to the parent process this should be // removed. See bug 1428130. - "cookie"_ns}; + "cookie"_ns, + + // Bug 28822: Make sure uitour permissions are preloaded in content + // processes. + "uitour"_ns};
// NOTE: nullptr can be passed as aType - if it is this function will return // "false" unconditionally. diff --git a/tools/lint/codespell.yml b/tools/lint/codespell.yml index e0144e5447cbb..55d8047aa519b 100644 --- a/tools/lint/codespell.yml +++ b/tools/lint/codespell.yml @@ -9,6 +9,7 @@ codespell: - browser/components/touchbar/docs/ - browser/components/urlbar/docs/ - browser/extensions/formautofill/locales/en-US/ + - browser/extensions/onboarding/locales/en-US/ - browser/extensions/report-site-issue/locales/en-US/ - browser/installer/windows/docs/ - browser/locales/en-US/
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 2b5033f50d7bf13bdbe2d2c9a77f66ad71f84689 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Wed Aug 8 11:34:40 2018 -0400
Bug 26961: New user onboarding.
Reuse the Firefox onboarding mechanism with minimal changes. Localizable strings are pulled in from Torbutton (if Torbutton is not installed, we lack about:tor and no tour will be shown). Replace SVG images with PNGs (see bug 27002), For defense in depth, omit include OnboardingTelemetry.jsm entirely. Added support for the following UITour page event: torBrowserOpenSecuritySettings
Also fix bug 27403: the onboarding bubble is not always displayed.
Arthur suggested to make the onboarding bubble visible on displays with less than 960px width available, so we choose 200px instead.
Also fix bug 28628: Change onboarding Security panel to open new Security Level panel.
Also fix bug 27484: Improve navigation within onboarding.
Bug 27082: enable a limited UITour
Disallow access to UITour functionality from all pages other than about:home, about:newtab, and about:tor. Implement a whitelist mechanism for page actions.
Bug 26962 - implement new features onboarding (part 1).
Add an "Explore" button to the "Circuit Display" panel within new user onboarding which opens the DuckDuckGo .onion and then guides users through a short circuit display tutorial.
Allow a few additional UITour actions while limiting as much as possible how it can be used.
Tweak the UITour styles to match the Tor Browser branding.
All user interface strings are retrieved from Torbutton's browserOnboarding.properties file.
Bug 27486 Avoid about:blank tabs when opening onboarding pages.
Instead of using a simple <a href>, programmatically open onboarding web pages by using tabBrowser.addTab(). The same technique is now used for "See My Path", "See FAQs", and "Visit an Onion".
Bug 29768: Introduce new features to users
Add an "update" tour for the Tor Browser 8.5 release that contains two panels: Toolbar and Security (with appropriate description text and images).
Display an attention-grabbing dot on the onboarding text bubble when the update tour is active. The animation lasts for 14 seconds.
Bug 31768: Introduce toolbar and network settings changes in onboarding
Update the "Tor Network" onboarding page to include a note that settings can now be accessed via the application preferences and add an "Adjust Your Tor Network Settings" action button which opens about:preferences#tor.
Replace the Tor Browser 8.5 "update" onboarding tour with a 9.0 one that includes the revised "Tor Network" page and a revised "Toolbar" page. The latter explains that Torbutton's toolbar item has been removed ("Goodbye Onion Button") and explains how to access the New Identity feature using the hamburger menu and new toolbar item.
Bug 34321 - Add Learn More onboarding item
Bug 40429: Update Onboarding for 10.5 --- browser/app/permissions | 10 +- browser/app/profile/001-base-profile.js | 6 + browser/base/content/main-popupset.inc.xhtml | 1 + browser/components/uitour/UITour-lib.js | 7 + browser/components/uitour/UITour.jsm | 80 ++- browser/components/uitour/UITourChild.jsm | 33 +- browser/extensions/onboarding/api.js | 43 +- .../extensions/onboarding/content/Onboarding.jsm | 391 +++++++++++- .../extensions/onboarding/content/img/close.png | Bin 0 -> 798 bytes .../onboarding/content/img/figure_addons.svg | 1 - .../onboarding/content/img/figure_customize.svg | 561 ----------------- .../onboarding/content/img/figure_default.svg | 1 - .../onboarding/content/img/figure_library.svg | 689 --------------------- .../onboarding/content/img/figure_performance.svg | 1 - .../onboarding/content/img/figure_private.svg | 1 - .../onboarding/content/img/figure_screenshots.svg | 191 ------ .../onboarding/content/img/figure_singlesearch.svg | 1 - .../onboarding/content/img/figure_sync.svg | 1 - .../content/img/figure_tor-circuit-display.png | Bin 0 -> 26334 bytes .../content/img/figure_tor-expect-differences.png | Bin 0 -> 22290 bytes .../onboarding/content/img/figure_tor-network.png | Bin 0 -> 11982 bytes .../content/img/figure_tor-onion-services.png | Bin 0 -> 40968 bytes .../onboarding/content/img/figure_tor-privacy.png | Bin 0 -> 35527 bytes .../content/img/figure_tor-security-level.png | Bin 0 -> 11263 bytes .../onboarding/content/img/figure_tor-security.png | Bin 0 -> 24554 bytes .../content/img/figure_tor-toolbar-layout.png | Bin 0 -> 13269 bytes .../onboarding/content/img/figure_tor-welcome.png | Bin 0 -> 48405 bytes .../onboarding/content/img/icons_addons.svg | 1 - .../onboarding/content/img/icons_customize.svg | 1 - .../onboarding/content/img/icons_default.svg | 1 - .../onboarding/content/img/icons_library.svg | 1 - .../onboarding/content/img/icons_no-icon.png | Bin 0 -> 673 bytes .../onboarding/content/img/icons_performance.svg | 1 - .../onboarding/content/img/icons_private.svg | 1 - .../onboarding/content/img/icons_screenshots.svg | 1 - .../onboarding/content/img/icons_singlesearch.svg | 1 - .../onboarding/content/img/icons_sync.svg | 1 - .../onboarding/content/img/icons_tour-complete.png | Bin 0 -> 694 bytes .../onboarding/content/img/icons_tour-complete.svg | 4 +- .../onboarding/content/img/watermark.svg | 1 - .../content/onboarding-tor-circuit-display.js | 283 +++++++++ .../onboarding/content/onboarding-tour-agent.js | 13 + .../extensions/onboarding/content/onboarding.css | 165 +++-- .../extensions/onboarding/content/onboarding.js | 3 +- browser/extensions/onboarding/jar.mn | 9 +- browser/extensions/onboarding/moz.build | 5 +- browser/themes/linux/browser.css | 9 - browser/themes/shared/UITour.inc.css | 56 +- browser/themes/windows/browser.css | 9 - intl/strres/nsStringBundle.cpp | 1 + 50 files changed, 956 insertions(+), 1629 deletions(-)
diff --git a/browser/app/permissions b/browser/app/permissions index 47eccccec5a13..d8439d49346b7 100644 --- a/browser/app/permissions +++ b/browser/app/permissions @@ -8,13 +8,9 @@ # See PermissionManager.cpp for more...
# UITour -# Bug 1557153: www.mozilla.org gets a special workaround in UITourChild.jsm -origin uitour 1 https://www.mozilla.org -origin uitour 1 https://monitor.firefox.com -origin uitour 1 https://screenshots.firefox.com -origin uitour 1 https://support.mozilla.org -origin uitour 1 about:home -origin uitour 1 about:newtab +# DuckDuckGo .onion (used for circuit display onboarding). +origin uitour 1 https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/ +origin uitour 1 about:tor
# Remote troubleshooting origin remote-troubleshooting 1 https://support.mozilla.org diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index b6383cabaa095..3584306e4f865 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -341,6 +341,12 @@ pref("browser.urlbar.update1.searchTips", false); // is only reported via telemetry (which is disabled). pref("corroborator.enabled", false);
+// Onboarding. +pref("browser.onboarding.tourset-version", 5); +pref("browser.onboarding.newtour", "welcome,privacy,tor-network-9.0,circuit-display,security,expect-differences,onion-services,learn-more"); +pref("browser.onboarding.updatetour", "learn-more"); +pref("browser.onboarding.skip-tour-button.hide", true); + // prefs to disable jump-list entries in the taskbar on Windows (see bug #12885) #ifdef XP_WIN // this pref changes the app's set AUMID to be dependent on the profile path, rather than diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml index 835948482381a..e5bf9460b75d1 100644 --- a/browser/base/content/main-popupset.inc.xhtml +++ b/browser/base/content/main-popupset.inc.xhtml @@ -242,6 +242,7 @@ <toolbarbutton id="UITourTooltipClose" class="close-icon" tooltiptext="&uiTour.infoPanel.close;"/> </hbox> + <toolbarseparator id="UITourTooltipToolbarSeparator"/> <description id="UITourTooltipDescription" flex="1"/> </vbox> </hbox> diff --git a/browser/components/uitour/UITour-lib.js b/browser/components/uitour/UITour-lib.js index 9ab8cd3aa68ca..87633285269ca 100644 --- a/browser/components/uitour/UITour-lib.js +++ b/browser/components/uitour/UITour-lib.js @@ -825,6 +825,13 @@ if (typeof Mozilla == "undefined") { Mozilla.UITour.closeTab = function() { _sendEvent("closeTab"); }; + + /** + * @summary Opens the Security Level Panel. + */ + Mozilla.UITour.torBrowserOpenSecurityLevelPanel = function() { + _sendEvent("torBrowserOpenSecurityLevelPanel"); + }; })();
// Make this library Require-able. diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index 29e8944e0e99b..4de7dbc64806c 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -68,6 +68,28 @@ ChromeUtils.defineModuleGetter( // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error". const PREF_LOG_LEVEL = "browser.uitour.loglevel";
+const TOR_BROWSER_PAGE_ACTIONS_ALLOWED = new Set([ + "showInfo", // restricted to TOR_BROWSER_TARGETS_ALLOWED + "showMenu", // restricted to TOR_BROWSER_MENUS_ALLOWED + "hideMenu", // restricted to TOR_BROWSER_MENUS_ALLOWED + "showHighlight", // restricted to TOR_BROWSER_TARGETS_ALLOWED + "hideHighlight", // restricted to TOR_BROWSER_TARGETS_ALLOWED + "openPreferences", + "closeTab", + "torBrowserOpenSecurityLevelPanel", +]); + +const TOR_BROWSER_TARGETS_ALLOWED = new Set([ + "torBrowser-newIdentityButton", + "torBrowser-circuitDisplay", + "torBrowser-circuitDisplay-diagram", + "torBrowser-circuitDisplay-newCircuitButton", +]); + +const TOR_BROWSER_MENUS_ALLOWED = new Set([ + "controlCenter", +]); + const BACKGROUND_PAGE_ACTIONS_ALLOWED = new Set([ "forceShowReaderIcon", "getConfiguration", @@ -111,6 +133,17 @@ var UITour = {
highlightEffects: ["random", "wobble", "zoom", "color"], targets: new Map([ + ["torBrowser-circuitDisplay", { + query: "#identity-icon", + }], + ["torBrowser-circuitDisplay-diagram", + torBrowserCircuitDisplayTarget("circuit-display-nodes")], + ["torBrowser-circuitDisplay-newCircuitButton", + torBrowserCircuitDisplayTarget("circuit-reload-button")], + ["torBrowser-newIdentityButton", { + query: "#new-identity-button", + }], + [ "accountStatus", { @@ -315,6 +348,11 @@ var UITour = { return false; }
+ if (!TOR_BROWSER_PAGE_ACTIONS_ALLOWED.has(action)) { + log.warn("Ignoring disallowed action:", action); + return false; + } + switch (action) { case "registerPageID": { break; @@ -653,6 +691,14 @@ var UITour = { this.showProtectionReport(window, browser); break; } + + case "torBrowserOpenSecurityLevelPanel": { + let securityLevelButton = + window.document.getElementById("security-level-button"); + if (securityLevelButton) + securityLevelButton.click(); + break; + } }
// For performance reasons, only call initForBrowser if we did something @@ -888,10 +934,7 @@ var UITour = {
// This function is copied to UITourListener. isSafeScheme(aURI) { - let allowedSchemes = new Set(["https", "about"]); - if (!Services.prefs.getBoolPref("browser.uitour.requireSecure")) { - allowedSchemes.add("http"); - } + let allowedSchemes = new Set(["about", "https"]);
if (!allowedSchemes.has(aURI.scheme)) { log.error("Unsafe scheme:", aURI.scheme); @@ -940,7 +983,10 @@ var UITour = { return Promise.reject("Invalid target name specified"); }
- let targetObject = this.targets.get(aTargetName); + let targetObject; + if (TOR_BROWSER_TARGETS_ALLOWED.has(aTargetName)) { + targetObject = this.targets.get(aTargetName); + } if (!targetObject) { log.warn( "getTarget: The specified target name is not in the allowed set" @@ -1407,6 +1453,10 @@ var UITour = { },
showMenu(aWindow, aMenuName, aOpenCallback = null, aOptions = {}) { + if (!TOR_BROWSER_MENUS_ALLOWED.has(aMenuName)) { + return; + } + log.debug("showMenu:", aMenuName); function openMenuButton(aMenuBtn) { if (!aMenuBtn || !aMenuBtn.hasMenu() || aMenuBtn.open) { @@ -1471,7 +1521,7 @@ var UITour = { if (aOpenCallback) { popup.addEventListener("popupshown", aOpenCallback, { once: true }); } - aWindow.document.getElementById("identity-box").click(); + aWindow.document.getElementById("identity-icon-box").click(); } else if (aMenuName == "pocket") { let button = aWindow.document.getElementById("save-to-pocket-button"); if (!button) { @@ -1506,6 +1556,10 @@ var UITour = { },
hideMenu(aWindow, aMenuName) { + if (!TOR_BROWSER_MENUS_ALLOWED.has(aMenuName)) { + return; + } + log.debug("hideMenu:", aMenuName); function closeMenuButton(aMenuBtn) { if (aMenuBtn && aMenuBtn.hasMenu()) { @@ -2031,6 +2085,20 @@ var UITour = { }, };
+function torBrowserCircuitDisplayTarget(aElemID) { + return { + infoPanelPosition: "rightcenter topleft", + query(aDocument) { + let popup = aDocument.defaultView.gIdentityHandler._identityPopup; + if (popup.state != "open") { + return null; + } + let element = aDocument.getElementById(aElemID); + return UITour.isElementVisible(element) ? element : null; + }, + }; +} + UITour.init();
/** diff --git a/browser/components/uitour/UITourChild.jsm b/browser/components/uitour/UITourChild.jsm index e2e763c8f4c13..02caae849de83 100644 --- a/browser/components/uitour/UITourChild.jsm +++ b/browser/components/uitour/UITourChild.jsm @@ -25,36 +25,9 @@ class UITourChild extends JSWindowActorChild { }); }
- isTestingOrigin(aURI) { - if ( - Services.prefs.getPrefType(PREF_TEST_WHITELIST) != - Services.prefs.PREF_STRING - ) { - return false; - } - - // Add any testing origins (comma-seperated) to the whitelist for the session. - for (let origin of Services.prefs - .getCharPref(PREF_TEST_WHITELIST) - .split(",")) { - try { - let testingURI = Services.io.newURI(origin); - if (aURI.prePath == testingURI.prePath) { - return true; - } - } catch (ex) { - Cu.reportError(ex); - } - } - return false; - } - // This function is copied from UITour.jsm. isSafeScheme(aURI) { - let allowedSchemes = new Set(["https", "about"]); - if (!Services.prefs.getBoolPref("browser.uitour.requireSecure")) { - allowedSchemes.add("http"); - } + let allowedSchemes = new Set(["about", "https"]);
if (!allowedSchemes.has(aURI.scheme)) { return false; @@ -90,9 +63,7 @@ class UITourChild extends JSWindowActorChild { return true; }
- // Bug 1557153: To allow Skyline messaging, workaround for UNKNOWN_ACTION - // overriding browser/app/permissions default - return uri.host == "www.mozilla.org" || this.isTestingOrigin(uri); + return false; }
receiveMessage(aMessage) { diff --git a/browser/extensions/onboarding/api.js b/browser/extensions/onboarding/api.js index dcdcb988451a2..aa583cb11d8ee 100644 --- a/browser/extensions/onboarding/api.js +++ b/browser/extensions/onboarding/api.js @@ -8,7 +8,6 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetters(this, { OnboardingTourType: "resource://onboarding/modules/OnboardingTourType.jsm", - OnboardingTelemetry: "resource://onboarding/modules/OnboardingTelemetry.jsm", Services: "resource://gre/modules/Services.jsm", UIState: "resource://services-sync/UIState.jsm", }); @@ -21,7 +20,11 @@ const RESOURCE_HOST = "onboarding";
const {PREF_STRING, PREF_BOOL, PREF_INT} = Ci.nsIPrefBranch;
-const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished"; +// In Tor Browser we initialize onboarding upon "final-ui-startup" instead +// of waiting for "browser-delayed-startup-finished"; otherwise, on first +// run the onboarding frame script's "onload" listener is installed too +// late to detect that about:tor is loaded. +const BROWSER_READY_NOTIFICATION = "final-ui-startup"; const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored"; const PREF_WHITELIST = [ ["browser.onboarding.enabled", PREF_BOOL], @@ -33,6 +36,19 @@ const PREF_WHITELIST = [ ];
[ + // Tor Browser tours: + "onboarding-tour-tor-welcome", + "onboarding-tour-tor-privacy", + "onboarding-tour-tor-network-9-0", + "onboarding-tour-tor-circuit-display", + "onboarding-tour-tor-security", + "onboarding-tour-tor-expect-differences", + "onboarding-tour-tor-onion-services", + "onboarding-tour-tor-toolbar-update-9-0", + "onboarding-tour-tor-learn-more", +#if 0 +// Firefox tours. To reduce conflicts when rebasing against newer Firefox +// code, we use the preprocessor to omit this code block. "onboarding-tour-addons", "onboarding-tour-customize", "onboarding-tour-default-browser", @@ -42,6 +58,7 @@ const PREF_WHITELIST = [ "onboarding-tour-screenshots", "onboarding-tour-singlesearch", "onboarding-tour-sync", +#endif ].forEach(tourId => PREF_WHITELIST.push([`browser.onboarding.tour.${tourId}.completed`, PREF_BOOL]));
let waitingForBrowserReady = true; @@ -82,6 +99,21 @@ function setPrefs(prefs) { }); }
+function openTorTab(aURL, aFrameScript) { + let win = Services.wm.getMostRecentWindow('navigator:browser'); + if (win) { + let tabBrowser = win.gBrowser; + let triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({}); + let tab = tabBrowser.addTab(aURL, { triggeringPrincipal }); + tabBrowser.selectedTab = tab; + + if (aFrameScript) { + let b = tabBrowser.getBrowserForTab(tab); + b.messageManager.loadFrameScript(aFrameScript, true); + } + } +} + /** * syncTourChecker listens to and maintains the login status inside, and can be * queried at any time once initialized. @@ -155,6 +187,11 @@ function initContentMessageListener() { isLoggedIn: syncTourChecker.isLoggedIn(), }); break; + case "tor-open-tab": + openTorTab(msg.data.params.url, msg.data.params.frameScriptURL); + break; +#if 0 +// No telemetry in Tor Browser. case "ping-centre": try { OnboardingTelemetry.process(msg.data.params.data); @@ -162,6 +199,7 @@ function initContentMessageListener() { Cu.reportError(e); } break; +#endif } }); } @@ -173,7 +211,6 @@ function onBrowserReady() { waitingForBrowserReady = false;
OnboardingTourType.check(); - OnboardingTelemetry.init(startupData); Services.mm.loadFrameScript("resource://onboarding/onboarding.js", true); initContentMessageListener(); } diff --git a/browser/extensions/onboarding/content/Onboarding.jsm b/browser/extensions/onboarding/content/Onboarding.jsm index de95a66632ab3..38c78f724b3b0 100644 --- a/browser/extensions/onboarding/content/Onboarding.jsm +++ b/browser/extensions/onboarding/content/Onboarding.jsm @@ -11,7 +11,10 @@ var EXPORTED_SYMBOLS = ["Onboarding"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css"; -const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties"; +const TORBUTTON_BUNDLE_URI = "chrome://torbutton/locale/browserOnboarding.properties"; +const TORBROWSER_WELCOME_TOUR_NAME_KEY = "onboarding.tour-tor-welcome"; +const BUNDLE_URI = "chrome://torbutton/locale/onboarding.properties"; +const BROWSER_BUNDLE_URI = "chrome://browser/locale/browser.properties"; const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js"; const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js"; const BRAND_SHORT_NAME = Services.strings @@ -20,8 +23,8 @@ const BRAND_SHORT_NAME = Services.strings const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count"; const NOTIFICATION_FINISHED_PREF = "browser.onboarding.notification.finished"; const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog"; -const ONBOARDING_MIN_WIDTH_PX = 960; -const SPEECH_BUBBLE_MIN_WIDTH_PX = 1365; +const ONBOARDING_MIN_WIDTH_PX = 200; +const SPEECH_BUBBLE_MIN_WIDTH_PX = 200; const SPEECH_BUBBLE_NEWTOUR_STRING_ID = "onboarding.overlay-icon-tooltip2"; const SPEECH_BUBBLE_UPDATETOUR_STRING_ID = "onboarding.overlay-icon-tooltip-updated2"; const ICON_STATE_WATERMARK = "watermark"; @@ -82,6 +85,194 @@ function createOnboardingTourButton(div, buttonId, l10nId, buttonElementTagName return aside; }
+// Tor Browser tours: +var onboardingTourset = { + // Tour items for new users: + "welcome": { + id: "onboarding-tour-tor-welcome", + tourNameId: TORBROWSER_WELCOME_TOUR_NAME_KEY, + instantComplete: true, + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-tor-welcome.title", "onboarding.tour-tor-welcome.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-welcome.png"); + createOnboardingTourButton(div, + "onboarding-tour-tor-welcome-button", "onboarding.tour-tor-welcome.next-button"); + + return div; + }, + }, + "privacy": { + id: "onboarding-tour-tor-privacy", + tourNameId: "onboarding.tour-tor-privacy", + instantComplete: true, + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-tor-privacy.title", "onboarding.tour-tor-privacy.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-privacy.png"); + createOnboardingTourButton(div, + "onboarding-tour-tor-privacy-button", "onboarding.tour-tor-privacy.button"); + + return div; + }, + }, + // In Tor Browser 9.0, we replaced the Tor Network panel with an updated one. + "tor-network-9.0": { + id: "onboarding-tour-tor-network-9-0", + tourNameId: "onboarding.tour-tor-network", + getPage(win) { + let div = win.document.createElement("div"); + + let desc = createOnboardingTourDescription(div, + "onboarding.tour-tor-network.title", "onboarding.tour-tor-network.description"); + let additionalDesc = win.document.createElement("p"); + additionalDesc.className = "onboarding-tour-description-para2"; + additionalDesc.setAttribute("data-l10n-id", + "onboarding.tour-tor-network.description-para2"); + desc.appendChild(additionalDesc); + + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-network.png"); + let btnContainer = createOnboardingTourButton(div, + "onboarding-tour-tor-network-action-button", "onboarding.tour-tor-network.action-button"); + btnContainer.className = "onboarding-tour-tor-action-button-container"; + + // The next button (right side) is a "Done" button if we are displaying + // the tour to users who updated their browser; otherwise, it is a + // button that takes the user to the next onboarding page. + let nextBtnID, nextBtnL10nID; + if (this._tourType === "update") { + // Using the onion services IDs here seems like a mistake, but it + // provides the functionality and translated string ("Done") we need. + nextBtnID = "onboarding-tour-tor-onion-services-next-button"; + nextBtnL10nID = "onboarding.tour-tor-onion-services.next-button"; + } else { + nextBtnID = "onboarding-tour-tor-network-button"; + nextBtnL10nID = "onboarding.tour-tor-network.button"; + } + createOnboardingTourButton(div, nextBtnID, nextBtnL10nID); + return div; + }, + }, + "circuit-display": { + id: "onboarding-tour-tor-circuit-display", + tourNameId: "onboarding.tour-tor-circuit-display", + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-tor-circuit-display.title", "onboarding.tour-tor-circuit-display.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-circuit-display.png"); + let btnContainer = createOnboardingTourButton(div, + "onboarding-tour-tor-circuit-display-button", "onboarding.tour-tor-circuit-display.button"); + btnContainer.className = "onboarding-tour-tor-action-button-container"; + createOnboardingTourButton(div, + "onboarding-tour-tor-circuit-display-next-button", "onboarding.tour-tor-circuit-display.next-button"); + + return div; + }, + }, + "security": { + id: "onboarding-tour-tor-security", + tourNameId: "onboarding.tour-tor-security", + getPage(win) { + let div = win.document.createElement("div"); + + let desc = createOnboardingTourDescription(div, + "onboarding.tour-tor-security.title", "onboarding.tour-tor-security.description"); + let additionalDesc = win.document.createElement("p"); + additionalDesc.className = "onboarding-tour-description-suffix"; + additionalDesc.setAttribute("data-l10n-id", + "onboarding.tour-tor-security.description-suffix"); + desc.appendChild(additionalDesc); + + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-security.png"); + let btnContainer = createOnboardingTourButton(div, + "onboarding-tour-tor-security-button", "onboarding.tour-tor-security-level.button"); + btnContainer.className = "onboarding-tour-tor-action-button-container"; + createOnboardingTourButton(div, + "onboarding-tour-tor-security-next-button", "onboarding.tour-tor-security-level.next-button"); + + return div; + }, + }, + "expect-differences": { + id: "onboarding-tour-tor-expect-differences", + tourNameId: "onboarding.tour-tor-expect-differences", + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-tor-expect-differences.title", "onboarding.tour-tor-expect-differences.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-expect-differences.png"); + let btnContainer = createOnboardingTourButton(div, + "onboarding-tour-tor-expect-differences-button", "onboarding.tour-tor-expect-differences.button"); + btnContainer.className = "onboarding-tour-tor-action-button-container"; + createOnboardingTourButton(div, + "onboarding-tour-tor-expect-differences-next-button", "onboarding.tour-tor-expect-differences.next-button"); + + return div; + }, + }, + "onion-services": { + id: "onboarding-tour-tor-onion-services", + tourNameId: "onboarding.tour-tor-onion-services", + getPage(win) { + let div = win.document.createElement("div"); + + createOnboardingTourDescription(div, + "onboarding.tour-tor-onion-services.title", "onboarding.tour-tor-onion-services.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-onion-services.png"); + let btnContainer = createOnboardingTourButton(div, + "onboarding-tour-tor-onion-services-button", "onboarding.tour-tor-onion-services.button"); + btnContainer.className = "onboarding-tour-tor-action-button-container"; + createOnboardingTourButton(div, + "onboarding-tour-tor-onion-services-next-button", "onboarding.tour-tor-onion-services.next-button"); + + return div; + }, + }, + "learn-more": { + id: "onboarding-tour-tor-learn-more", + // Re-use "Learn More" string from Firefox langpacks + tourNameId: "getUserMedia.shareScreen.learnMoreLabel", + highlightId: "onboarding.tour-tor-update.prefix-new", + getPage(win) { + return win.document.createElement("div"); + }, + }, + // Tour items for users who have updated their Tor Browser: + "toolbar-update-9.0": { + id: "onboarding-tour-tor-toolbar-update-9-0", + tourNameId: "onboarding.tour-tor-toolbar", + getPage(win) { + let div = win.document.createElement("div"); + + let desc = createOnboardingTourDescription(div, + "onboarding.tour-tor-toolbar-update-9.0.title", "onboarding.tour-tor-toolbar-update-9.0.description"); + let additionalDesc = win.document.createElement("p"); + additionalDesc.className = "onboarding-tour-description-para2"; + additionalDesc.setAttribute("data-l10n-id", + "onboarding.tour-tor-toolbar-update-9.0.description-para2"); + desc.appendChild(additionalDesc); + + createOnboardingTourContent(div, "resource://onboarding/img/figure_tor-toolbar-layout.png"); + let btnContainer = createOnboardingTourButton(div, + "onboarding-tour-tor-toolbar-update-9-0-button", "onboarding.tour-tor-toolbar-update-9.0.button"); + btnContainer.className = "onboarding-tour-tor-action-button-container"; + createOnboardingTourButton(div, + "onboarding-tour-tor-toolbar-next-button", "onboarding.tour-tor-toolbar-update-9.0.next-button"); + + return div; + }, + }, +}; +#if 0 +// Firefox tours. To reduce conflicts when rebasing against newer Firefox +// code, we use the preprocessor to omit this code block. /** * Add any number of tours, key is the tourId, value should follow the format below * "tourId": { // The short tour id which could be saved in pref @@ -415,6 +606,7 @@ var onboardingTourset = { }, }, }; +#endif
/** * The script won't be initialized if we turned off onboarding by @@ -473,7 +665,10 @@ class Onboarding { // We want to create and append elements after CSS is loaded so // no flash of style changes and no additional reflow. await this._loadCSS(); - this._bundle = Services.strings.createBundle(BUNDLE_URI); + this._bundle = new _TorOnboardingStringBundle(); + if (!this._bundle.inited) { + return; + }
this._loadJS(UITOUR_JS_URI);
@@ -515,7 +710,11 @@ class Onboarding { }
_resizeUI() { - this._windowWidth = this._window.document.body.getBoundingClientRect().width; + // In Tor Browser we check against innerWidth instead of against the + // body's bounding rect because about:tor keeps its body hidden until + // the Tor status is known, and the bounding rect is zero while the + // body is hidden. + this._windowWidth = this._window.innerWidth; if (this._windowWidth < ONBOARDING_MIN_WIDTH_PX) { // Don't show the overlay UI before we get to a better, responsive design. this.destroy(); @@ -523,11 +722,18 @@ class Onboarding { }
this._initUI(); - if (this._isFirstSession && this._windowWidth >= SPEECH_BUBBLE_MIN_WIDTH_PX) { + // For Tor Browser, show the "Let's get started" speech bubble until each + // tour item has been completed. + let isTourComplete = (ICON_STATE_WATERMARK == + Services.prefs.getStringPref("browser.onboarding.state", + ICON_STATE_DEFAULT)); + if ((!isTourComplete || this._isFirstSession) && + this._windowWidth >= SPEECH_BUBBLE_MIN_WIDTH_PX) { this._overlayIcon.classList.add("onboarding-speech-bubble"); } else { this._overlayIcon.classList.remove("onboarding-speech-bubble"); } + this.updateAttentionDot(); }
_initUI() { @@ -542,7 +748,10 @@ class Onboarding { this._overlayIcon = this._renderOverlayButton(); this._overlayIcon.addEventListener("click", this); this._overlayIcon.addEventListener("keypress", this); - body.insertBefore(this._overlayIcon, body.firstChild); + let buttonContainer = this._window.document.createElement("div"); + buttonContainer.id = "onboarding-overlay-button-container"; + buttonContainer.appendChild(this._overlayIcon); + body.insertBefore(buttonContainer, body.firstChild);
this._overlay = this._renderOverlay(); this._overlay.addEventListener("click", this); @@ -556,7 +765,8 @@ class Onboarding { this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
// Doing tour notification takes some effort. Let's do it on idle. - this._window.requestIdleCallback(() => this.showNotification()); +// For now, onboarding notifications are disabled in Tor Browser. +// this._window.requestIdleCallback(() => this.showNotification()); }
_getTourIDList() { @@ -698,19 +908,30 @@ class Onboarding { ({ id, classList } = target.firstChild); }
+ const kOnionURL = "https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/"; // DuckDuckGo + const kLearnMore = "https://www.torproject.org/releases/tor-browser-11-0/"; + let handledTourActionClick = false; switch (id) { case "onboarding-overlay-button-icon": case "onboarding-overlay-button": - this.telemetry({ - type: "onboarding-logo-click", - bubble_state: this._bubbleState, - logo_state: this._logoState, - notification_state: this._notificationState, - session_key: this._session_key, - width: this._windowWidthRounded, - }); - this.showOverlay(); - this.gotoPage(this._firstUncompleteTour.id); + // If this instance upgraded, then directly open the release notes + // when the bubble is clicked. + if (this._tourType === "update") { + this.sendMessageToChrome("tor-open-tab", {url: kLearnMore}); + // Mark item as complete + this.setToursCompleted(["onboarding-tour-tor-learn-more"]); + } else { + this.telemetry({ + type: "onboarding-logo-click", + bubble_state: this._bubbleState, + logo_state: this._logoState, + notification_state: this._notificationState, + session_key: this._session_key, + width: this._windowWidthRounded, + }); + this.showOverlay(); + this.gotoPage(this._firstUncompleteTour.id); + } break; case "onboarding-skip-tour-button": this.hideNotification(); @@ -767,6 +988,36 @@ class Onboarding { this.gotoPage(tourId); this._removeTourFromNotificationQueue(tourId); break; + case "onboarding-tour-tor-welcome-button": + case "onboarding-tour-tor-privacy-button": + case "onboarding-tour-tor-network-button": + case "onboarding-tour-tor-circuit-display-next-button": + case "onboarding-tour-tor-security-next-button": + case "onboarding-tour-tor-expect-differences-next-button": + case "onboarding-tour-tor-toolbar-next-button": + this.gotoNextTourItem(); + handledTourActionClick = true; + break; + case "onboarding-tour-tor-circuit-display-button": + let kFrameScript = "resource://onboarding/onboarding-tor-circuit-display.js"; + this.sendMessageToChrome("tor-open-tab", + {url: kOnionURL, frameScriptURL: kFrameScript}); + break; + case "onboarding-tour-tor-expect-differences-button": + const kFAQURL = "https://support.torproject.org/#faq"; + this.sendMessageToChrome("tor-open-tab", {url: kFAQURL}); + break; + case "onboarding-tour-tor-onion-services-button": + this.sendMessageToChrome("tor-open-tab", {url: kOnionURL}); + break; + // Open the Release Notes webpage and hide the overlay. + case "onboarding-tour-tor-onion-services-next-button": + case "onboarding-tour-tor-learn-more": + this.sendMessageToChrome("tor-open-tab", {url: kLearnMore}); + this.hideOverlay(); + // Mark item as complete + this.setToursCompleted(["onboarding-tour-tor-learn-more"]); + break; } if (classList.contains("onboarding-tour-item")) { this.telemetry({ @@ -780,7 +1031,8 @@ class Onboarding { // Keep focus (not visible) on current item for potential keyboard // navigation. target.focus(); - } else if (classList.contains("onboarding-tour-action-button")) { + } else if (!handledTourActionClick && + classList.contains("onboarding-tour-action-button")) { let activeTourId = this._activeTourId; this.setToursCompleted([ activeTourId ]); this.telemetry({ @@ -793,6 +1045,21 @@ class Onboarding { } }
+ gotoNextTourItem() { + let activeTourID = this._activeTourId; + if (activeTourID) { + let idx = this._tourItems.findIndex(item => (item.id === activeTourID)); + if (idx >= 0) { + // If at the end of the list, close onboarding; otherwise, go to next. + if (++idx >= this._tourItems.length) { + this.hideOverlay(); + } else { + this.gotoPage(this._tourItems[idx].id); + } + } + } + } + /** * Wrap keyboard focus within the dialog. * When moving forward, focus on the first element when the current focused @@ -950,7 +1217,9 @@ class Onboarding { this._overlayIcon.dispatchEvent(new this._window.CustomEvent("Agent:Destroy"));
this._clearPrefObserver(); + let buttonContainer = this._overlayIcon.parentElement; this._overlayIcon.remove(); + buttonContainer.remove(); if (this._overlay) { // send overlay-session telemetry this.hideOverlay(); @@ -974,9 +1243,21 @@ class Onboarding { this._overlayIcon.classList.add("onboarding-watermark"); break; } + this.updateAttentionDot(); return true; }
+ // Display an attention-grabbing dot on the speech bubble if the + // bubble is visible and we are showing the "update" tour. + updateAttentionDot() { + let buttonContainer = this._overlayIcon.parentElement; + if ((this._bubbleState === "bubble") && (this._tourType === "update")) { + buttonContainer.classList.add("onboarding-overlay-attention-dot"); + } else { + buttonContainer.classList.remove("onboarding-overlay-attention-dot"); + } + } + showOverlay() { if (this._tourItems.length == 0) { // Lazy loading until first toggle. @@ -1237,6 +1518,7 @@ class Onboarding { // After the notification mute on the 1st session, // we don't want to show the speech bubble by default this._overlayIcon.classList.remove("onboarding-speech-bubble"); + this.updateAttentionDot();
let queue = this._getNotificationQueue(); let totalMaxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-all-tours-ms"); @@ -1422,7 +1704,8 @@ class Onboarding {
let header = this._window.document.createElement("header"); header.id = "onboarding-header"; - header.textContent = this._bundle.GetStringFromName("onboarding.overlay-title2"); +// In Tor Browser, we do not want header text. +// header.textContent = this._bundle.GetStringFromName("onboarding.overlay-title2"); this._dialog.appendChild(header);
let nav = this._window.document.createElement("nav"); @@ -1487,12 +1770,6 @@ class Onboarding { defaultImg.src = Services.prefs.getStringPref("browser.onboarding.default-icon-src", "chrome://branding/content/icon64.png"); button.appendChild(defaultImg); - let watermarkImg = this._window.document.createElement("img"); - watermarkImg.id = "onboarding-overlay-button-watermark-icon"; - watermarkImg.setAttribute("role", "presentation"); - watermarkImg.src = Services.prefs.getStringPref("browser.onboarding.watermark-icon-src", - "resource://onboarding/img/watermark.svg"); - button.appendChild(watermarkImg); return button; }
@@ -1522,7 +1799,17 @@ class Onboarding { let tourPanelId = `${tour.id}-page`; tab.setAttribute("aria-controls", tourPanelId);
+ if (tour.highlightId) { + // Add [New] or [Updated] text after this navigation item to draw + // attention to it. + let highlight = this._window.document.createElement("span"); + highlight.className = "onboarding-tour-description-highlight"; + highlight.textContent = this._bundle.GetStringFromName(tour.highlightId); + tab.appendChild(highlight); + } + li.appendChild(tab); + itemsFrag.appendChild(li); // Dynamically create tour pages let div = tour.getPage.call(this, this._window, this._bundle); @@ -1579,3 +1866,55 @@ class Onboarding { doc.head.appendChild(script); } } + +// _TorOnboardingStringBundle implements the subset of the nsIStringBundle +// that is used by the code in this file. It checks first for strings inside +// Torbutton's browserOnboarding.properties file and secondarily in Firefox's +// onboarding.properties file. Finally, it looks for the string within +// browser.properties. +class _TorOnboardingStringBundle { + constructor() { + this._mBrowserBundle = Services.strings.createBundle(BROWSER_BUNDLE_URI); + this._mFirefoxBundle = Services.strings.createBundle(BUNDLE_URI); + this._mTorButtonBundle = Services.strings.createBundle(TORBUTTON_BUNDLE_URI); + + // If the Tor Browser onboarding strings which ship inside Torbutton are + // not available, fail initialization so that no tours are shown. + try { + let result = this._mTorButtonBundle.GetStringFromName( + TORBROWSER_WELCOME_TOUR_NAME_KEY); + this.inited = true; + } catch (e) {} + } + + GetStringFromName(aName) { + let result; + try { + result = this._mTorButtonBundle.GetStringFromName(aName); + } catch (e) { + try { + result = this._mFirefoxBundle.GetStringFromName(aName); + } catch (e) { + result = this._mBrowserBundle.GetStringFromName(aName); + } + } + return result; + } + + formatStringFromName(aName, aParams, aLength) { + let result; + try { + result = this._mTorButtonBundle.formatStringFromName(aName, aParams, + aLength); + } catch (e) { + try { + result = this._mFirefoxBundle.formatStringFromName(aName, aParams, + aLength); + } catch (e) { + result = this._mBrowserBundle.formatStringFromName(aName, aParams, + aLength); + } + } + return result; + } +} diff --git a/browser/extensions/onboarding/content/img/close.png b/browser/extensions/onboarding/content/img/close.png new file mode 100644 index 0000000000000..8a637de879ec7 Binary files /dev/null and b/browser/extensions/onboarding/content/img/close.png differ diff --git a/browser/extensions/onboarding/content/img/figure_addons.svg b/browser/extensions/onboarding/content/img/figure_addons.svg deleted file mode 100644 index b5f056737f118..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_addons.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="295" height="199" viewBox="0 0 295 199" xmlns="http://www.w3.org/2000/svg"><title>addons</title><defs><linearGradient x1="-3335.765%" y1="-2236.632%" x2="5558.543%" y2="3780.103%" id="a"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradient x1="-251.09%" y1="-799.657%" x2="413.095%" y2="1054.368%" id="b"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradien [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_customize.svg b/browser/extensions/onboarding/content/img/figure_customize.svg deleted file mode 100644 index 0c0cb30df5dc7..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_customize.svg +++ /dev/null @@ -1,561 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="295" height="238"> - <defs> - <linearGradient id="a" x1="-678.179817%" x2="218.03211%" y1="-1879.5122%" y2="503.09878%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="b" x1="-2438.15968%" x2="713.035484%" y1="-2346.83281%" y2="705.8875%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="c" x1="-1876.47349%" x2="477.431325%" y1="-2215.7169%" y2="536.030986%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="d" x1="-300.502319%" x2="326.878731%" y1="-277.869139%" y2="301.876261%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="e" x1="-556.386842%" x2="471.897895%" y1="-1050.94952%" y2="809.757143%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="f" x1="-2301.11875%" x2="1769.175%" y1="-4460.38%" y2="3354.584%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="g" x1="-14090.38%" x2="5447.03%" y1="-14085.94%" y2="5451.47%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="h" x1="-1245.88053%" x2="483.093805%" y1="-2962.82857%" y2="1024.39796%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="i" x1="-4762.32308%" x2="1072.27051%" y1="-2525.31233%" y2="591.799315%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="j" x1="-419.785061%" x2="175.867683%" y1="-263.047589%" y2="146.541719%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="k" x1="-13945.16%" x2="5592.25%" y1="-13931.16%" y2="5606.26%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="l" x1="-93.8791876%" x2="171.036409%" y1="-368.29%" y2="383.149231%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="m" x1="-105.119971%" x2="175.589943%" y1="-106.702736%" y2="160.566895%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="n" x1="-4526.45652%" x2="3968.06957%" y1="-3864.98889%" y2="3371.08889%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="o" x1="-1590.58053%" x2="2387.43252%" y1="-835.835705%" y2="1325.72397%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="p" x1="-1174.27536%" x2="1657.23333%" y1="-1275.87873%" y2="1781.26242%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="q" x1="-8557.56%" x2="10979.85%" y1="-4234.38%" y2="5534.325%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="r" x1="-949.737079%" x2="1245.47865%" y1="-1023.81277%" y2="1336.75514%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="s" x1="-850.555238%" x2="1010.15048%" y1="-759.279881%" y2="912.10717%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="t" x1="-2526.775%" x2="962.048214%" y1="-2513.94763%" y2="949.261152%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="u" x1="-953.117868%" x2="406.88755%" y1="-1083.71008%" y2="471.112383%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="v" x1="-1736.94827%" x2="671.463404%" y1="-2238.58822%" y2="855.656147%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="w" x1="-9592.54%" x2="9944.87%" y1="-9613.77%" y2="9923.64%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="x" x1="-546.9251%" x2="669.232184%" y1="-637.97868%" y2="716.339388%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="y" x1="-2626.25%" x2="2515.17368%" y1="-10166.57%" y2="9370.85%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="z" x1="-26076.58%" x2="9092.02%" y1="-26064.58%" y2="9104.02%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="A" x1="-11996.8348%" x2="3293.86087%" y1="-4084.84179%" y2="1164.20299%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="B" x1="-1988.44219%" x2="759.104687%" y1="-1576.81875%" y2="621.219375%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="C" x1="-4889.30185%" x2="1623.40185%" y1="-2351.25495%" y2="817.087387%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="D" x1="-2655.5559%" x2="951.48%" y1="-6714.61282%" y2="2302.97692%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="E" x1="-11418.996%" x2="2648.448%" y1="-28603.67%" y2="6564.93%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="F" x1="-1067.54883%" x2="792.163033%" y1="-899.682353%" y2="691.657014%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="G" x1="-3245.82558%" x2="2272.05861%" y1="-2753.32267%" y2="1935.824%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="H" x1="-835.133806%" x2="827.684161%" y1="-835.133806%" y2="827.684161%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="I" x1="-4541.82131%" x2="1223.52295%" y1="-2322.54576%" y2="657.84322%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="J" x1="-2057.47051%" x2="889.742903%" y1="-1738.77914%" y2="791.335971%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="K" x1="-1278.62667%" x2="1189.34526%" y1="-1278.9986%" y2="1188.97333%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="L" x1="-6112.0075%" x2="2680.1425%" y1="-6270.03333%" y2="2747.55641%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="M" x1="-1115.93023%" x2="572.391158%" y1="-1175.6355%" y2="582.7945%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="N" x1="-9656.07586%" x2="2471.02759%" y1="-9322.84667%" y2="2400.02%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="O" x1="-7887.73698%" x2="3321.17237%" y1="-6188.2325%" y2="2603.9175%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="P" x1="-984.783738%" x2="288.77261%" y1="-1902.68288%" y2="506.125342%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="Q" x1="-2522.67732%" x2="1102.95155%" y1="-5039.01837%" y2="2138.24694%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="R" x1="-5921.7225%" x2="2870.4275%" y1="-6075.45385%" y2="2942.1359%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="S" x1="-5881.53%" x2="2910.62%" y1="-5881.26%" y2="2910.89%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="T" x1="-5841.3375%" x2="2950.8125%" y1="-5841.4525%" y2="2950.6975%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="U" x1="-7423.23691%" x2="3785.67244%" y1="-5801.6425%" y2="2990.5075%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="V" x1="-4020.34%" x2="1003.74571%" y1="-2527.16182%" y2="669.983636%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="W" x1="-4517.96032%" x2="1064.35714%" y1="-5480.38654%" y2="1282.80577%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="X" x1="-3834.66828%" x2="2163.11753%" y1="-3992.49299%" y2="2248.99581%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="Y" x1="-132.800878%" x2="141.123835%" y1="-126.933901%" y2="145.268963%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="Z" x1="-8624.4%" x2="10913.01%" y1="-4751.06111%" y2="6103.05556%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="aa" x1="-20576.83%" x2="14591.77%" y1="-11391.2944%" y2="8146.81667%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ab" x1="-3210.85073%" x2="1716.38147%" y1="-3721.57455%" y2="1963.19067%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ac" x1="-964.539164%" x2="305.324758%" y1="-1877.16986%" y2="531.638356%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ad" x1="-5971.9075%" x2="2820.24%" y1="-7463.6%" y2="3526.5875%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ae" x1="-3626.20024%" x2="2128.73795%" y1="-3780.54791%" y2="2217.23789%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="af" x1="-3545.17742%" x2="2127.17742%" y1="-3793.28448%" y2="2270.26724%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ag" x1="-8571.16538%" x2="4955.21923%" y1="-4812.20217%" y2="2833.14565%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ah" x1="-921.592388%" x2="295.314187%" y1="-948.070803%" y2="335.454745%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ai" x1="-1521.4596%" x2="706.721231%" y1="-1247.46875%" y2="591.922626%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aj" x1="-678.258824%" x2="423.307164%" y1="-682.475952%" y2="429.068947%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ak" x1="-6036.96%" x2="2755.19%" y1="-6038.3275%" y2="2753.82%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="al" x1="-876.033667%" x2="359.821607%" y1="-805.490909%" y2="336.346753%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="am" x1="-6523.57663%" x2="4813.74946%" y1="-5038.58141%" y2="3749.13318%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="an" x1="-2645.94937%" x2="963.166315%" y1="-6683.46667%" y2="2334.12564%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ao" x1="-6631.98345%" x2="4705.34265%" y1="-5121.96932%" y2="3665.74527%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ap" x1="-1435.66843%" x2="1068.42563%" y1="-2846.04456%" y2="2010.54343%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aq" x1="-2633.78646%" x2="975.329221%" y1="-6654.88205%" y2="2362.70769%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ar" x1="-2206.3925%" x2="2189.6825%" y1="-2444.83034%" y2="2406.01103%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="as" x1="-5385.00363%" x2="1874.66412%" y1="-10484.884%" y2="3582.556%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="at" x1="-2391.91311%" x2="1397.1783%" y1="-5593.4125%" y2="3198.7375%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="au" x1="-2264.71662%" x2="1521.15732%" y1="-5306.3925%" y2="3485.7575%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="av" x1="-8124.26538%" x2="5402.11923%" y1="-4560.45%" y2="3084.89783%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aw" x1="-651.882139%" x2="479.56521%" y1="-1403.71323%" y2="934.962067%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ax" x1="-782.651586%" x2="579.099454%" y1="-1688.18577%" y2="1133.37245%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="ay" x1="-2808.00445%" x2="930.963547%" y1="-4874.39455%" y2="1519.89636%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="az" x1="-3080.27111%" x2="827.351111%" y1="-4651.45333%" y2="1209.98%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aA" x1="-17842.03%" x2="17326.57%" y1="-17824.13%" y2="17344.47%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aB" x1="-4927.80617%" x2="7466.4141%" y1="-2177.67416%" y2="3371.61183%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aC" x1="-20583.89%" x2="14584.71%" y1="-5842.07714%" y2="4206.09429%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aD" x1="-13953.96%" x2="21214.64%" y1="-2172.57143%" y2="3409.74603%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aE" x1="-13796.3%" x2="21372.3%" y1="-1986.00882%" y2="3185.84412%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aF" x1="-13888.17%" x2="21280.43%" y1="-2353.96379%" y2="3709.58793%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aG" x1="-9372.00909%" x2="6613.71818%" y1="-2958.36812%" y2="2138.53043%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aH" x1="-16384.5222%" x2="12067.4729%" y1="-4573.9%" y2="3418.96364%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aI" x1="-17462.5%" x2="5983.23333%" y1="-13777.5842%" y2="4732.21053%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aJ" x1="-7480.69%" x2="7500.95%" y1="-7483.33%" y2="7498.32%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aK" x1="-7021.27187%" x2="3968.91562%" y1="-20520.9909%" y2="11450.4636%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aL" x1="-9826.0913%" x2="5464.60435%" y1="-22671.15%" y2="12497.45%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aM" x1="-2964.13075%" x2="2873.3758%" y1="-3993.57709%" y2="3854.15587%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aN" x1="-2330.22879%" x2="2205.28384%" y1="-2914.60952%" y2="2667.70794%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aO" x1="-1407.98283%" x2="1424.97017%" y1="-1728.51863%" y2="1719.38333%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aP" x1="-1807.9102%" x2="1780.72245%" y1="-2740.56%" y2="2669.99385%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aQ" x1="-1472.82%" x2="1783.415%" y1="-4365.0426%" y2="5068.41814%"> - <stop stop-color="#FFFBCC" offset="0%"/> - <stop stop-color="#FFC9D5" offset="100%"/> - </linearGradient> - <linearGradient id="aR" x1="-511.087979%" x2="436.292949%" y1="-431.133333%" y2="359.905%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - <linearGradient id="aS" x1="-2336.83483%" x2="1396.15506%" y1="-7055.5%" y2="4019.03333%"> - <stop stop-color="#FFE900" offset="18.75%"/> - <stop stop-color="#FF0039" offset="100%"/> - </linearGradient> - </defs> - <g fill="none" fill-rule="evenodd"> - <path d="M149.5 168.5c-.1 0-.1.1-.2.1l-3.3 1.5c-.2.1-.3.1-.5.2.7.3 1.4.5 2.2.5 1.6 0 3.1-.7 4.2-1.9 1-1.1 1.4-2.5 1.3-4-.1-.9-.3-1.7-.7-2.4l-1.6 4.4c-.3.6-.8 1.2-1.4 1.6zM178.7 206.1c-.1-.1-.2-.3-.2-.4l-2 2.7 3.1 1.1-.8-2.6c-.1-.2-.1-.5-.1-.8zM240.6 207.9h0zM168.5 200.6h-.2c-.2.2-.5.3-.7.4l-2.5.7.2.8c1.1.7 2 1.7 2.5 2.9l1 .4 3.7-5c.9-1.2 2.2-1.9 3.7-2l-.1-.3-2.5.7c-.2.1-.4.1-.6.1h-.2c-.2.2-.5.3-.7.4l-3.1.9c-.1-.1-.3 0-.5 0zM146.9 159.8c.1.1.2.1.3.2 0-.1.1-.2.1-.3-.1 0-.2 0-.4.1zM143. [...] - <path fill="#EDEDF0" fill-rule="nonzero" d="M227.5 226.4c.1.1.1.1.2.1 0 0-.1 0-.2-.1z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M228.2 231c-1.2 0-2.4-.4-3.4-1.2-1.3-1.1-2.1-2-2.4-7.2-3.4 0-6.7.1-9.9.2.6 3.3.2 4.4-.7 5.6-1 1.4-2.6 2.2-4.3 2.2-2.9 0-5.3-2.1-9.6-7-15.1 1.3-25.3 3.8-25.3 6.6 0 4.3 23.1 7.7 51.6 7.7s51.6-3.4 51.6-7.7c0-3.6-16.7-6.7-39.3-7.5-2.3 5.7-5.1 8.3-8.3 8.3z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M158.9 75.5h13.4c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-13.4c-.3 0-.6.2-.6.6.1.4.3.6.6.6zM155.4 85.7c0-.3-.2-.6-.6-.6h-13.4c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h13.4c.3-.1.6-.3.6-.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M134.3 114.7l.6-.4.4-.2c0-.7.1-1.3.2-2 0-.1.1-.2.1-.4-.4-.9-.8-2-1.2-3v6h-.1zM131.8 102.3c-.1-.3 0-.6.3-.7.3-.1.6 0 .7.3l.3.9V67h-13c.7 2.2 1.8 5.2 3.1 8.8.1.3 0 .6-.3.7h-.2c-.2 0-.4-.1-.5-.4-1.3-3.8-2.4-7-3.2-9.2h-3.4c1.1 3.8 3.1 10.1 5.8 18.2l-.1-.5c1.6 4.4 8.9 24.1 11.5 31l.4-.3v-9.5c-.6-1.4-1.1-2.7-1.4-3.5zM121.2 91.2c-3.9-10.9-6.6-19.6-7.9-24.2H7.1v98.7c0 .6 0 .9.1 1 .1 0 .4.1 1 .1h124c.6 0 .9 0 1-.1 0-.1.1-.4.1-1v-38.4l-1.6 1-.4.2c-.3.2- [...] - <path fill="#FFF" fill-rule="nonzero" d="M70.3 103.8c-5.6 0-10.2 4.6-10.2 10.2s4.6 10.2 10.2 10.2 10.2-4.6 10.2-10.2c.1-5.6-4.5-10.2-10.2-10.2zM137.7 124.4l-.9.6.9 2.1v-2.7zM135.3 121.7s0 .1 0 0l2.4-1.5v-.1l-2.4 1.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M134.8 126.3l-.5.3v39.1c0 1.9-.3 2.2-2.2 2.2H8.1c-1.9 0-2.2-.3-2.2-2.2V65.8h107c-.2-.8-.4-1.4-.4-1.8-.1-.6.3-1.2.9-1.3.6-.1 1.2.3 1.3.9.1.4.3 1.2.6 2.2h3.4l-.8-2.4c-.1-.3.1-.6.4-.7.3-.1.6.1.7.4 0 0 .3 1 .9 2.7h14.5v39.7c.6 1.5 1.3 3.1 1.8 4.4.4-.9.9-1.6 1.6-2.3V49.7c0-2.3-1.9-4.2-4.2-4.2H6.8c-2.3 0-4.2 1.9-4.2 4.2v118c0 2 1.8 3.7 3.9 3.7h127.3c1 0 1.9-.4 2.6-.9-.8-1.6-1.2-3.4-1.3-5.3 0-1.5.9-2.7 2.2-3.3-.8-.8-1.1-2-.7-3.1l1.1-2.9v-23.4c-1-2.1- [...] - <path fill="#FFF" fill-rule="nonzero" d="M129.1 125.6c-.1.1-.2.2-.4.2-.2.1-.4.1-.6.1.2 0 .4 0 .6-.1.2 0 .3-.1.4-.2l4.1-2.5v-.1l-4.1 2.6z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M137.7 120.2v.1l2.2-1.5M139 115.8c-.2-.5-.2-1-.3-1.5 0 .5.1 1 .3 1.5z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M133.8 171.4H6.5c-2.2 0-3.9-1.6-3.9-3.7v-118c0-2.3 1.9-4.2 4.2-4.2h126.6c2.3 0 4.2 1.9 4.2 4.2V107.6c.6-.7 1.4-1.3 2.2-1.8V81.2h27.6c.1-.2.2-.4.2-.6 0-.2.3-.2.3 0 .1.2.1.4.2.6h14.5c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-42.8V49.7c0-3.6-2.9-6.5-6.5-6.5H6.8c-3.6 0-6.5 2.9-6.5 6.5v118c0 3.3 2.8 5.9 6.1 5.9h127.3c1.4 0 2.7-.5 3.7-1.2-.4-.6-.8-1.3-1.1-1.9-.7.5-1.6.9-2.5.9z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M137.7 127.1l-.9-2.1-1.9 1.2c.9 2 1.9 4.1 2.9 6.3V156l1.2-3.2c.2-.5.6-1 1-1.3v-14.1c2.6 5.5 5.2 11 7.4 15.2h.4c.7 0 1.4.1 2.1.2-3.1-6.1-6.1-12.1-8.7-17.9-.2-.5-.7-1.5-1.2-2.7V123l-2.2 1.4v2.7h-.1z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M134.3 65.8h-14.5c-.6-1.7-.9-2.7-.9-2.7-.1-.3-.4-.4-.7-.4-.3.1-.4.4-.4.7l.8 2.4h-3.4c-.3-1-.5-1.8-.6-2.2-.1-.6-.7-1-1.3-.9-.6.1-1 .7-.9 1.3.1.4.2 1 .4 1.8H6v99.8c0 1.9.3 2.2 2.2 2.2h124c1.9 0 2.2-.3 2.2-2.2v-39.1l-1.1.7v38.4c0 .6 0 .9-.1 1-.1 0-.4.1-1 .1H8.2c-.6 0-.9 0-1-.1 0-.1-.1-.4-.1-1V67h106.2c1.3 4.6 4 13.3 7.9 24.2h.1c2.5 7.2 7.1 19.3 9.6 25.7l2-1.2c-2.7-6.9-9.9-26.7-11.5-31l.1.5c-2.8-8.1-4.7-14.4-5.8-18.2h3.4c.8 2.2 1.8 5.4 3.2 9.2. [...] - <path fill="#D7D7DB" fill-rule="nonzero" d="M95.6 109.8h-7.1c-.4-2.1-1.2-4-2.3-5.8l5.1-5.1c.7-.9 1-2 .8-3.1-.2-1.1-.7-2.1-1.6-2.8-.7-.6-1.6-.8-2.5-.8-.9 0-1.8.3-2.6.9l-5.1 5.1c-1.8-1.1-3.7-1.8-5.8-2.3v-7.1c0-2.3-1.9-4.2-4.2-4.2-2.3 0-4.2 1.9-4.2 4.2v7.1c-2.1.4-4 1.2-5.8 2.3l-4.7-5.1c-.8-.8-2-1.3-3.1-1.3-1.2 0-2.3.5-3.1 1.3-.8.8-1.3 2-1.3 3.1 0 1.2.5 2.3 1.3 3.2l5.1 4.7c-1.1 1.8-1.8 3.7-2.3 5.8H45c-2.3 0-4.2 1.9-4.2 4.2 0 2.3 1.9 4.2 4.2 4.2h7.1c.4 2.1 1.2 4 2.3 5.8l-5 4.7c-1.9 1.4-2. [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M33.7 25.5h97.9c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-22.8c-2-3.7-7.1-11.7-13.4-12.9-8.4-1.6-10 6.7-10 6.7S79.8 2.6 65.8 4.5c-6.5.9-9 4.2-9.8 7.8h.1c.3 0 .6.2.6.5 0 .4.1.7.1 1.1 0 .3-.2.6-.5.6h-.1c-.2 0-.4-.1-.5-.3-.1 1.9.1 3.8.5 5.3H57c-.1-.3-.2-.6-.4-1-.1-.3.1-.6.4-.7.3-.1.6.1.7.4.3 1 .6 1.7.6 1.7.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-1.3c.4 1.5.9 2.5.9 2.7H33.7c-.6 0-1.1.5-1.1 1.1 0 .5.5 1 1.1 1z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M205.5 42.3c.1 0 .3-.1.4-.2.6-.7 1.5-1.1 2.6-1.4.3-.1.5-.4.4-.7-.1-.3-.4-.5-.7-.4-1.3.4-2.4.9-3.1 1.7-.2.2-.2.6 0 .8.1.2.3.2.4.2zM212.7 40.5c.4.1.7.2 1 .3h.2c.2 0 .5-.1.5-.4.1-.3-.1-.6-.4-.7-.4-.1-.8-.2-1.1-.3-.3-.1-.6.1-.7.4 0 .4.2.7.5.7zM238.3 50.7h3.3c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-3.3c-.3 0-.6.2-.6.6 0 .4.3.6.6.6zM221.2 46.7c.3-1 1.2-3.2 3.8-3.2.3 0 .7 0 1 .1 1.6.3 3.2 1.3 4.8 3 .2.2.6.2.8 0 .2-.2.2-.6 0-.8-1.8-1.9-3.6-3-5.4-3.4-.4-. [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M191.7 54.6h54.4c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-12.9c-1.1-2.1-3.9-6.5-7.4-7.2-2.4-.5-3.8.5-4.6 1.6 0 .1-.1.2-.2.3-.6 1-.8 1.9-.8 1.9s-3.1-8-10.9-7c-5.7.8-6 5-5.4 7.8h.7s.1-.1.2-.1c.3-.1.6 0 .7.3l.1.1c.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-.9c.2.9.5 1.4.5 1.5h-13.2.2c-.6 0-1.1.5-1.1 1.1-.1.6.4 1.1 1.1 1.1z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M107.4 231.1c-4 0-5.8-2.5-6.2-4.6l-.1-.5c-7 .5-12.1 2.1-12.1 4 0 2.3 7.3 4.1 16.3 4.1s16.3-1.8 16.3-4.1c0-1.4-2.7-2.6-6.7-3.3-.2.7-.6 1.3-1 1.9-2 2.4-5.7 2.5-6.5 2.5z"/> - <path fill="#FFF" fill-rule="nonzero" d="M227.3 225.7c-.1-.3-.1-.6-.1-1 0 .4 0 .7.1 1zM228 226.5h-.1.1zM226.9 216v0zM199.5 218.8c.3.3.5.6.8.9-.3-.3-.6-.6-.8-.9z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M237.7 208.7c1-.3 1.9-.5 2.8-.8h.1c6.5-2 12.4-4.7 17.4-7.6-7.2 3.5-15 5-20 5.6 0 1-.1 1.9-.3 2.8z"/> - <path fill="#FFF" fill-rule="nonzero" d="M241.9 163c.1-.2.2-.4.2-.6 0 .2-.1.4-.2.6zM234.1 70.2c-.3 0-.5-.1-.8-.1-.3 0-.7 0-1 .1.3 0 .7-.1 1-.1.2 0 .5 0 .8.1zM232 70.2c-2.5.4-4.6 2.2-5.5 4.5.9-2.3 3-4 5.5-4.5zM219.1 84.9c0-.1 0-.1 0 0 0-.1 0-.1 0 0zM221.2 79.8c.5-.5 1.1-.9 1.7-1.3-.6.3-1.2.8-1.7 1.3zM226 76.7c-.4.3-.7.7-1 1-.7.1-1.4.4-2.1.7.6-.3 1.3-.6 2.1-.7.3-.3.7-.6 1-1zM207.3 226.3s0-.1 0 0h-.1c0-.1.1 0 .1 0zM263.6 172.3c-.4.1-.8.3-1.2.4.4-.1.8-.2 1.2-.4zM248.7 65.6h.8c-.3.1-.5 0- [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M235.8 218c-.5 1.4-1.1 3.2-2 4.9-1.5 3.1-3.5 5.9-5.8 5.9-.7 0-1.3-.2-1.8-.6-.6-.5-1.2-.9-1.4-5.4-.1-1.5-.1-3.5-.1-6.2-.5 0-1-.1-1.6-.2l-.7-.2c0 2.8 0 4.9.1 6.6.2 5.1 1 6.1 2.4 7.2 1 .8 2.1 1.2 3.4 1.2 3.2 0 6-2.7 8.4-8.1.6-1.3 1.2-2.8 1.7-4.4.7-2 1.3-4.8 1.9-8.2-.9.3-1.9.5-2.9.8-.5 2.6-1.1 4.9-1.6 6.7zM265 198.7c-2.4 1.6-5 3.1-7.8 4.7 1.6-.7 3.1-1.4 4.6-2.3h.2c3.7 0 7.1-.5 10.2-1.4-1.7-.2-3.3-.6-4.7-1.3-.8.1-1.6.2-2.5.3z"/> - <path fill="#FFF" fill-rule="nonzero" d="M284.9 173c.8-.7 1.8-1.2 2.9-1.2.4 0 .7 0 1 .1h.1c-.7-.6-1.5-1.1-2.4-1.2-.3-.1-.6-.1-.8-.1-.8 0-1.6.2-2.3.6l-.2.2c.6.6 1.2 1.1 1.7 1.6zM287.7 188c.3-.6.6-1.1.9-1.7-.4.3-.8.6-1.3.8.1.4.3.6.4.9z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M266.3 154.9c-1.2.6-2.1.9-2.7 1.2-.3.1-.6.2-.8.3-.2.1-.3.1-.3.1-.2.1-.4.1-.7.1-.6 0-1.2-.3-1.7-.7-6.4 3.3-13.7 6.8-16.1 7.9-.1.2-.2.4-.2.6.8-.1 1.7-.1 2.5-.1 3.5 0 6.8.6 9.7 1.8 2.7 1.1 5 2.5 7 4.3 6.4-2.4 7.9-7.8 7.9-8 .2-.9.9-1.5 1.8-1.7h.3c.8 0 1.5.4 1.9 1.1.1.2 1.7 2.9 2.3 7.1 1 .2 2 .6 2.9 1-.5-5.5-2.5-9.1-2.9-9.7-.9-1.4-2.4-2.3-4-2.3-.2 0-.5 0-.7.1-1.9.3-3.4 1.7-3.9 3.5 0 .1-1 3.6-5.1 5.7-1.9-1.4-4-2.7-6.4-3.7-1.3-.6-2.8-1-4.2-1.3 5.1 [...] - <path fill="#FFF" fill-rule="nonzero" d="M265.3 137.7h.5-.5zM246.3 166.4h-.3H246.3z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M284.3 126.3l1.2-1.8c4.6-2.2 7.4-7.2 6.8-12.3v-.2c1.4-2.3 2-5 1.7-7.7-.3-2.4-1.3-4.6-2.8-6.4-.3-2.1-.7-4.1-1.3-6.1v-1c0-4-2-7.7-5.3-9.9-2.8-4.1-6.5-7.8-10.6-10.6-1.8-4.4-6.2-7.3-11-7.3-1.4 0-2.9.3-4.3.8-.8-.2-1.6-.4-2.4-.5-2.1-1.6-4.7-2.5-7.3-2.5-.5 0-1 0-1.5.1-2.2.3-4.4 1.2-6.1 2.6-2.1.4-4.2 1.1-6.2 1.9-.6-.1-1.1-.1-1.7-.1-5.2 0-9.9 3.5-11.4 8.4-4.4 1.8-7.4 6.2-7.4 11.1v.2c-.2.5-.4 1-.6 1.4-.4.4-.8.7-1.2 1.2-.1-2.2-1.1-4.2-2.2-6.3-.2-.4-.4 [...] - <path fill="#FFF" fill-rule="nonzero" d="M287.7 101.9c-.4-.6-.9-1.1-1.4-1.5.6.4 1 .9 1.4 1.5zM286.9 111.2c.2.6.4 1.2.5 1.9 0 .2 0 .5.1.7 0-.2 0-.5-.1-.7-.1-.7-.3-1.3-.5-1.9zM289 106c0-.3 0-.6-.1-.9 0-.4-.1-.7-.2-1.1.1.3.2.7.2 1.1.1.3.1.6.1.9zM263.6 137.5c-.3-.1-.7-.1-1-.2h-.1.1c.3.1.6.1 1 .2zM259.6 140.2c.4-.1.7-.2 1.1-.4-.4.2-.7.4-1.1.4zM188.5 192.2v0zM218.2 88.3c-.2.4-.4.9-.5 1.3-.2.1-.3.1-.5.2.1-.1.3-.2.5-.2.1-.4.3-.9.5-1.3zM186 170.6c0-.1-.1-.1-.1-.2 0 .1 0 .2.1.2zM215.8 97.7c0-. [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M207.5 230.7c1.7 0 3.3-.8 4.3-2.2.9-1.2 1.3-2.3.7-5.6-.3-2-1.1-4.8-2.2-8.8 1 0 2 .1 3 .1h2.1l-3.8-1.1-4.8-.6c1.6 5.2 2.4 8.5 2.9 10.6.6 3.2.3 3.7-.2 4.3-.5.8-1.4 1.2-2.3 1.2-1.7 0-3.4-1.3-6.6-4.9-.8-.9-1.8-2-2.9-3.3-3.3-3.8-5.6-8.6-7.1-12.7-1-.5-2-1.1-2.9-1.7 1.6 4.8 4.3 11 8.4 15.8.6.8 1.3 1.5 1.8 2.1 4.4 4.8 6.7 6.8 9.6 6.8zM185.9 201.9c1.2.9 2.4 1.7 3.6 2.5l-.3-.9c-2.1-3-3.1-7-3-11.4-.4-.3-.8-.5-1.2-.8-.2-.2-.3-.5-.1-.8.2-.2.5-.3.8-.1.2. [...] - <path fill="#FFF" fill-rule="nonzero" d="M206.8 158.8c.5-.4 1-.9 1.6-1.3-.6.4-1.1.9-1.6 1.3z"/> - <path fill="url(#a)" fill-rule="nonzero" d="M237.4 200.4c-.1 1-.2 2-.2 2.9 4.1-.4 10.5-1.6 17.1-4.5 1.7-.8 3.3-1.6 4.7-2.6-.7-.2-1.4-.6-1.9-1.1-3.5 1.9-8.3 4-14.2 4.8-1.9.2-3.7.4-5.5.5z"/> - <path fill="url(#b)" fill-rule="nonzero" d="M268.8 169.3c1.6-.6 3.4-.9 5.1-.9.4 0 .7 0 1.1.1-.4-2.3-1.1-4-1.5-4.9-.1-.3-.3-.5-.3-.6v-.1s0 .1-.1.3c0 .1-.1.2-.1.3-.1.1-.1.3-.2.5-.7 1.2-1.8 3.3-4 5.3z"/> - <path fill="url(#c)" fill-rule="nonzero" d="M274.9 176.9c-.3 0-.6-.1-.9-.1-2.6 0-5 1.4-6.3 3.6-.3.5-.7 1-1.1 1.4l1.6.4c0-.1.1-.2.2-.3l.9-.7c.2-.2.6-.1.8.1.2.2.1.6-.1.8l-.5.4.9.2c.8.2 1.5.6 2 1.2.7-1.4 1.3-2.8 1.8-4.1.2-1 .5-1.9.7-2.9z"/> - <path fill="url(#d)" fill-rule="nonzero" d="M189.9 156.3c-2.8-1.8-6-2.6-9.1-2.6-5.6 0-11.1 2.8-14.3 7.8-5 7.9-2.7 18.3 5.2 23.3 2.8 1.8 6 2.6 9.1 2.6 2.1 0 4.2-.4 6.1-1.1.3-1.5.7-3.1 1.2-4.6-.5 0-1-.1-1.5-.4-1.5-.8-2.1-2.6-1.3-4s2.6-1.9 4.1-1.1c.3.2.5.3.7.5.6-1.3 1.3-2.6 2-3.9-3.2.4-4.2.5-4.7.5h-.6c-1-.1-2.3-.6-2.9-1.8-.5-.8-.7-2.3.5-4.3.5-.7 1.1-1.9 10.6-5.9-1.4-1.9-3-3.7-5.1-5zm-14.1 2.1c1.7 0 3.1 1.3 3.1 2.9 0 1.6-1.4 2.9-3.1 2.9-1.7 0-3.1-1.3-3.1-2.9 0-1.6 1.4-2.9 3.1-2.9zm-8.7 1 [...] - <path fill="url(#e)" fill-rule="nonzero" d="M204.7 160.7c-9.4 3.5-17.8 8.2-17.9 8.3-.1.1-.2.1-.3.1-.2 0-.4-.1-.5-.3-.2.3-.3.6-.3.9v.6c0 .1 0 .2.1.2 0 .1.1.1.1.2.4.5 1.2.5 1.2.5H188c.2 0 .4 0 .6-.1.3 0 .6-.1.9-.1h.2c.3 0 .6-.1.9-.1h.2c.4 0 .7-.1 1.1-.2h.1c.9-.1 2-.3 3.1-.5 2.9-4 5-6.2 5.1-6.4.2-.2.6-.2.8 0 .1.1.2.2.2.4 1.1-1.2 2.2-2.3 3.5-3.5z"/> - <path fill="url(#f)" fill-rule="nonzero" d="M187.9 167.1c-.1 0-.1.1-.2.1s-.1.1-.2.1c1.1-.6 2.7-1.4 4.8-2.5-1.8.9-3.4 1.7-4.4 2.3z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M213.3 204.7c-.7-.2-1.3-.7-1.7-1.2l-.4 1.3 1.9.5c.3-.1.7-.2 1-.3l-.8-.3z"/> - <path fill="#FFF" fill-rule="nonzero" d="M188.7 193.1c0 .1-.1.1-.1.1v1.6c0 .4.1.8.2 1.2 0 .2.1.4.1.5l.3 1.2c0 .2.1.3.1.5.1.4.3.8.4 1.2v.1c.8 1.6 2.9 4.5 8.2 5.4.3.1.5.3.4.6-.1.3-.3.5-.5.5h-.1c-2.7-.5-4.6-1.5-6-2.5l.3.9c.2.5.3 1 .5 1.5 1.4.7 2.7 1.2 4.1 1.7 2.4.8 4.8 1.4 7.2 1.9 0-.1-.1-.3-.1-.4 0-.1-.1-.3-.1-.4-.2-.5-.4-1-.5-1.5-.1-.3-.2-.6-.3-.8h.2c0-.6 0-1.1.2-1.7l1.2-4.1c-.7-.2-1.5-.4-2.2-.6-.3-.1-.5-.4-.4-.7.1-.3.4-.5.7-.4.7.2 1.5.4 2.2.6l4.1-14.3c.7-2.4 2.9-4 5.4-4 .5 0 1 .1 1.6 [...] - <path fill="url(#g)" fill-rule="nonzero" d="M203.6 209.1c0-.1 0-.1 0 0-.1-.2-.2-.3-.2-.4.1.1.1.2.2.4z"/> - <path fill="url(#h)" fill-rule="nonzero" d="M222.3 203.8l-1.9-.6c0 .1 0 .2-.1.3-.4 1.3-1.6 2.2-2.9 2.2-.3 0-.6 0-.9-.1l-2.4-.7c-.3.1-.7.2-1 .3l10 2.9 1.3-4.5c-.4.2-.8.3-1.3.3-.2.1-.5 0-.8-.1z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M227.1 224.7c0 .4.1.7.1 1 0 .3.1.5.2.6 0 .1.1.1.1.1.1.1.1.1.2.1H228.3s.1 0 .1-.1c.1 0 .1-.1.2-.1l.1-.1c.1 0 .1-.1.2-.1l.1-.1.2-.2.1-.1c.1-.1.2-.2.2-.3l.1-.1c.1-.2.3-.3.4-.5v-.1c.1-.2.2-.3.4-.5 0-.1.1-.2.1-.2.1-.1.2-.3.3-.4.1-.1.1-.2.2-.3.1-.1.1-.2.2-.3-1.4 0-2.9-.1-4.3-.1v.9c.2.2.2.5.2.9z"/> - <path fill="url(#i)" fill-rule="nonzero" d="M230 212.6c-.5 1.6-1.6 2.8-3.1 3.5v7.5c0 .4.1.7.1 1.1 0 .4.1.7.1 1 0 .3.1.5.2.6 0 .1.1.1.1.1.1.1.1.1.2.1H228.2s.1 0 .1-.1c.1 0 .1-.1.2-.1l.1-.1c.1 0 .1-.1.2-.1l.1-.1.2-.2.1-.1c.1-.1.2-.2.2-.3l.1-.1c.1-.2.3-.3.4-.5v-.1c.1-.2.2-.3.4-.5 0-.1.1-.2.1-.2.1-.1.2-.3.3-.4.1-.1.1-.2.2-.3.1-.1.1-.2.2-.3 0 0 0-.1.1-.1.1-.1.1-.2.2-.3.1-.2.2-.3.2-.5.1-.1.1-.2.2-.4s.2-.4.2-.5c.1-.1.1-.3.2-.4.1-.2.2-.4.2-.6.1-.1.1-.3.2-.4.1-.2.2-.5.3-.7 0-.1.1-.2.1-.4.1-.4 [...] - <path fill="url(#j)" fill-rule="nonzero" d="M240.1 167.1c-.1.2-.3.3-.5.3h-.2c-.3-.1-.4-.4-.3-.7.4-.9.9-2.1 1.4-3.2-5.6 9-7.5 16.2-9.5 22.5l.9.3c1.4.4 2.6 1.4 3.3 2.7.7 1.3.9 2.8.5 4.3l-4.9 17.1c1.6-.3 3.2-.6 4.7-.9v-.1-.1c.1-.6.2-1.1.2-1.7v-.1c0-.2.1-.5.1-.7 0-.1-.1-.2 0-.3.5-4.5 1.9-23.7 1.9-23.9 0-.3.3-.5.6-.5s.5.3.5.6c0 .1-.7 9.5-1.3 16.7 1.7-.1 3.5-.2 5.3-.5 5.6-.8 10.3-2.8 13.7-4.7-.5-.9-.6-2-.4-3l1.9-7.3c.4-1.4 1.4-2.4 2.6-2.8-.9-1.2-1-2.9-.3-4.3.8-1.6 2-3.1 3.3-4.3-.4.1-.8.3-1 [...] - <path fill="url(#k)" fill-rule="nonzero" d="M202.7 206.4c.1.2.2.5.3.8 0-.3-.1-.5-.1-.8h-.2z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M194.4 210.8c.3.6.6 1.3 1 1.9 0 .1.1.1.1.2.3.6.7 1.3 1.1 1.9 0 .1.1.1.1.2l1.2 1.8.1.1c.5.6.9 1.3 1.5 1.9.3.3.5.6.8.9.1.1.1.2.2.2l.6.6c.1.1.2.2.2.3.2.2.3.4.5.5.1.1.2.2.2.3.2.2.3.3.4.5.1.1.2.2.2.3l.5.5.2.2.2.2.4.4.1.1.5.5.2.2.3.3.2.2.3.3c.1.1.1.1.2.1.1.1.2.1.3.2l.1.1c.1.1.2.1.3.2 0 0 .1 0 .1.1.1.1.2.1.3.2h.1c.1 0 .2.1.2.1h.6c.1 0 .2-.1.2-.2 0 0 .1-.1.1-.2v-.1-.3-.1c0-.2 0-.4-.1-.6v-.2c0-.2-.1-.4-.1-.6v-.2c0-.2-.1-.4-.1-.6v-.2-.1c-.1-.3-.1-.6- [...] - <path fill="url(#l)" fill-rule="nonzero" d="M241.8 136.3c-7.4 7.4-10.4 9.3-19.4 11-14.7 1.3-34.1-.7-39.2-3.2-5.6-2.7-12-17.9-12-18.1-.1-.5-.5-.8-1-.8-.4 2.5.9 6.1 2.9 9.7.3.6.7 1.2 1.1 1.8.6.9 1.2 1.8 1.8 2.6.4.6.8 1.1 1.2 1.6.4.5.8 1 1.3 1.5.2.2.4.5.6.7.4.4.8.8 1.3 1.2.2.2.4.3.6.5.8.6 1.6 1.1 2.3 1.4 6.8 2.5 16.4 4.1 26.3 4.1 1.7 0 3.4-.1 5.1-.2h.2c.1 0 12.1-1.3 17.8-3.6.3-.1.6 0 .7.3.1.3 0 .6-.3.7-3.8 1.5-10.1 2.6-14.2 3.2-.3.2-.6.3-1 .5 10.2 0 19.5-1.3 23.1-4.7 4.3-4 3.1-12.5.8-10.2z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M250.4 158.2c-3.6 1.7-6.5 3.1-7.6 3.6 2.1-1 4.8-2.2 7.6-3.6z"/> - <path fill="url(#m)" fill-rule="nonzero" d="M224.6 99.7c.7 0 1.4-.1 2.1-.1v-.2c0-.3.3-.6.6-.5l3.3.1c.3-1.1.7-1.7.8-1.7.2-.2.5-.3.8-.1.2.2.3.5.1.8-.1.1-1.4 1.9-.1 4.9.6 1.5 2.9 2.8 3.8 3.2.2.1.3.3.3.5 0 0 .4 4.6 3.5 8.4 3.4 4.2 8.4 5.7 8.5 5.7.2.1.4.2.4.4 0 0 .6 3.3 1.9 4.6.7.7 2.6 2.6 7.4 1.7.3-.1.6.1.6.4.1.3-.1.6-.4.6-1 .2-1.9.3-2.7.3-3.1 0-4.7-1.2-5.8-2.2-1.3-1.3-1.9-3.9-2.1-4.8-1.1-.4-4.5-1.7-7.4-4.6.6 1 1.2 1.9 2 2.9v.1c0 3.5 2.3 6.4 5.4 7.4.7 1.5 1.3 3.2 1.6 5.3h.1c.3-.1.6.1.7.4 [...] - <path fill="url(#n)" fill-rule="nonzero" d="M233 105.8c.5.6 1.1 1.2 1.8 1.6.1.3.3.7.5 1.1-.1-.6-.2-1.1-.3-1.4-.4-.3-1.2-.7-2-1.3z"/> - <path fill="url(#o)" fill-rule="nonzero" d="M202.5 90.4c1.3 1.2 3.4.6 4.4-.9v-.1c0-.1.3-5.4-2.2-7.4 0 0-.1 0-.1.1l-.2.2s-.1.1-.1.2c-.1.1-.1.2-.2.3 0 .1-.1.1-.1.2-.1.1-.1.2-.2.4 0 .1-.1.1-.1.2-.1.2-.2.3-.3.5 0 .1-.1.1-.1.2-.1.2-.2.5-.3.7 0 .1 0 .1-.1.2-.1.2-.2.4-.2.6 0 .1-.1.2-.1.3-.1.2-.1.3-.2.5 0 .1-.1.2-.1.3 0 .2-.1.3-.1.5 0 .1 0 .2-.1.3 0 .1-.1.3-.1.4V89.7c0 .1 0 .2.1.3v.2c0 .1.1.3.2.3.1-.2.1-.2.2-.1z"/> - <path fill="url(#p)" fill-rule="nonzero" d="M209.1 94.2V94c-.1-.5-.3-1.4-.7-2.6-.1-.3-.2-.5-.3-.7-.6 2.1-3.7 3.3-5.8 1.5 0 .4-.1.7-.1 1.1 0 .6 0 1.1.1 1.6s.2 1 .4 1.3c.1.1.1.2.2.2.1.1.3.2.4.3.1.1.3.1.4.2 2.1.7 4.6-.6 5.4-2.7z"/> - <path fill="url(#q)" fill-rule="nonzero" d="M204 98c0 .3.1.5.1.8-.1-.7-.2-1.3-.3-2 .1.4.1.8.2 1.2z"/> - <path fill="url(#r)" fill-rule="nonzero" d="M210.6 95.5c-.4 2.7-3.6 4.7-6.5 3.5v.2c.1.4.2.8.2 1.1.4 1.6 1 2.9 1.6 3.4 2.8.5 6.9-1.5 7.1-4.5v-.5c-.2-.6-.5-1.4-1.1-2.1-.4-.4-.9-.8-1.3-1.1z"/> - <path fill="url(#s)" fill-rule="nonzero" d="M214.2 112c-.1 0-.1-.1 0 0-.2-.4 0-.7.3-.8.2-.1 3.9-1.4 3.9-5.2 0-2.6-2.1-4.5-3.5-5.5.2 3.4-3.6 6.1-7 6 1.5 2.5 3.4 3 3.5 3 .6.1 1 .7.9 1.3-.1.5-.6.9-1.1.9.2.2.5.3.7.3.6.3 1.5.2 2.3 0z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M240.4 163.4c-.5 1.1-1 2.3-1.4 3.2-.1.3 0 .6.3.7h.2c.2 0 .4-.1.5-.3.7-1.7 1.6-3.7 2.1-4.6.1-.1.1-.3.2-.4.2-.1.3-.2.5-.2 1-.5 4-1.9 7.6-3.6 3-1.4 6.1-3 9-4.5 0-.3.1-.6.3-.9 0 0 0-.1.1-.2-5.5 2.5-17.4 8.1-18 8.5-.3.2-.8 1.2-1.4 2.3z"/> - <path fill="url(#t)" fill-rule="nonzero" d="M275.8 140c-.7.2-1.3.6-2 .9v.4c.4 1.9 1.8 3.4 3.5 4.2.5-.6.9-1.1 1.3-1.7.3-.5.6-.9.8-1.3-.8-.2-1.5-.6-2-1.1-.4-.5-.7-1-.9-1.5-.2-.1-.4 0-.7.1z"/> - <path fill="url(#u)" fill-rule="nonzero" d="M273.3 149.1c.1 0 .1-.1.2-.1l.6-.5c.2-.1.4-.3.6-.4.1-.1.2-.2.3-.2-.3-.2-.7-.4-1-.7-1.4-1.1-2.5-2.7-3-4.4-.1.1-.2.1-.3.2-.2.1-.4.3-.6.4l-.6.5c-.2.2-.4.3-.6.5-.6.5-1.2 1-1.7 1.5-.6.5-1.1 1-1.6 1.5-.9.9-1.8 1.9-2.6 2.9s-1.4 1.8-1.7 2.2c-.2.3-.3.5-.4.7l-.1.2c-.2.3-.2.7-.1 1 .1.4.3.7.6.8.3.2.7.2 1 .1 0 0 .1 0 .3-.1.2-.1.4-.1.7-.3.6-.2 1.4-.6 2.6-1.1h.1c-1.2-2.3-.6-5.1-.6-5.3.1-.6.7-1 1.3-.8.4.1.7.4.8.8 1.8-.4 4.1 0 5.8.6z"/> - <path fill="url(#v)" fill-rule="nonzero" d="M281.4 141.3c.9-.2 2.7-.9 4.2-3.5-1-.8-2.6.8-3.3-.6-.6-1.1.1-1.5-.4-2.1h-.6c-1.6 0-2.9.7-3.5 1.9-.6 1.1-.3 2.5.6 3.5.6.8 1.8 1.1 3 .8z"/> - <path fill="#FFF" fill-rule="nonzero" d="M267.5 148.5c.1.2.1.4 0 .6 0 0-.5 2.5.5 4.1.5.8 1.3 1.2 2.4 1.4 1.3.2 2.3 0 3.1-.6 1.2-.9 1.4-2.7 1.4-2.7 0-.4.3-.7.6-.9-.3-.3-.7-.5-1.1-.8-.3-.2-.7-.4-1.2-.5-1.6-.6-3.9-1-5.7-.6z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M148 139.7c-.3.1-.4.5-.3.7 2 4.3 4 8.2 6 12 .1.2.3.3.5.3.1 0 .2 0 .3-.1.3-.1.4-.5.2-.8-2-3.7-4-7.6-6-11.9-.1-.2-.4-.4-.7-.2zM160.4 163.3c.1 0 .2 0 .3-.1.3-.2.3-.5.2-.8-.6-.9-1.2-1.9-1.8-2.8-.1-.1-.2-.2-.4-.3.6 1.2 1 2.4 1.2 3.7.2.2.4.3.5.3zM143.3 129.9h-.2v.4l.4 1c.1.2.3.3.5.3h.2c.3-.1.4-.5.3-.7l-.4-1h-.8zM283.4 171.4c1.5-1.3 3.1-2.7 4.6-4.1.2-.2.2-.6 0-.8-.2-.2-.6-.2-.8 0-1.7 1.5-3.3 3-5 4.4.3.2.6.4.9.7.2-.1.2-.2.3-.2zM185 190.5c-.2.2-.1.6 [...] - <path fill="#FFFEFE" fill-rule="nonzero" d="M235.2 188.8c-.7-1.3-1.9-2.3-3.3-2.7l-.9-.3-15.3-4.4c-.5-.1-1-.2-1.6-.2-2.5 0-4.7 1.7-5.4 4l-4.1 14.3-.3 1.1-1.2 4.1c-.2.6-.2 1.1-.2 1.7 0 .3 0 .5.1.8.1.5.2 1 .5 1.5.1.1.1.2.1.3 0 0 0 .1.1.1.1.2.2.4.4.5.7 1 1.7 1.7 2.9 2.1l4.8 1.4 3.8 1.1 7 2 .7.2c.5.1 1 .2 1.6.2.8 0 1.5-.2 2.2-.5 1.5-.7 2.6-1.9 3.1-3.5l.7-2.3 4.9-17.1c.3-1.5.1-3.1-.6-4.4zm-1.7 3.7l-5.6 19.4c-.4 1.5-1.8 2.4-3.2 2.4-.3 0-.6 0-.9-.1l-16.2-4.7c-1.8-.5-2.8-2.4-2.3-4.2l5.6-19.4c [...] - <path fill="#FFFEFE" fill-rule="nonzero" d="M228.7 191.1l-12.9-3.7c-.2 0-.3-.1-.5-.1-.7 0-1.4.5-1.6 1.2l-4.7 16.2c-.3.9.3 1.8 1.2 2.1l12.9 3.7c.2 0 .3.1.5.1.7 0 1.4-.5 1.6-1.2l4.7-16.2c.2-.9-.4-1.9-1.2-2.1zm-14.4 7.2c.1-.4.4-.6.8-.6h.2l8.1 2.3c.4.1.7.6.6 1-.1.4-.4.6-.8.6h-.2l-8.1-2.3c-.5-.1-.7-.5-.6-1zm-.9 3.2c.1-.4.4-.6.8-.6h.2l3.2.9c.4.1.7.6.6 1-.1.4-.4.6-.8.6h-.2l-3.2-.9c-.5 0-.8-.5-.6-1zm9.7 6.7l-10-2.9-1.9-.5.4-1.3c.4.6 1 1 1.7 1.2l.9.2 2.4.7c.3.1.6.1.9.1 1.4 0 2.6-.9 2.9-2.2 0- [...] - <path fill="#FFFEFE" fill-rule="nonzero" d="M166.9 216.4c-.3-.9-1.1-1.5-2-1.5-.2 0-.4 0-.6.1-1.1.3-1.7 1.5-1.4 2.6.3.9 1.1 1.5 2 1.5.2 0 .4 0 .6-.1h.2c1-.3 1.6-1.4 1.3-2.4-.1-.1-.1-.2-.1-.2zM163.9 207.1c-.3-.8-1.1-1.3-1.9-1.3-.3 0-.5 0-.8.1-1.1.4-1.6 1.6-1.2 2.7.3.8 1.1 1.3 1.9 1.3.3 0 .5 0 .8-.1.1 0 .1 0 .2-.1 1-.4 1.5-1.5 1.1-2.5l-.1-.1zM136.1 109.9c-.3.6-.5 1.2-.6 1.8 0 .1-.1.2-.1.4-.1.7-.2 1.3-.2 2l-.4.2-.6.4-.9.6-.2.1-.4.3-2 1.2-2.7 1.7-2.3 1.4c-.3.1-.5.3-.7.5-1.8 1.4-2.5 3.9-1. [...] - <path fill="#D7D7DB" fill-rule="nonzero" d="M154.8 144.4l-2.2-4.7c-.1-.3-.5-.4-.7-.3-.3.1-.4.5-.3.7l2.2 4.7c.1.2.3.3.5.3.1 0 .2 0 .2-.1.3 0 .4-.4.3-.6zM161 185.3c.1.2.3.2.5.2.1 0 .2 0 .3-.1.3-.2.3-.5.1-.8l-2.5-3.5c-.2-.3-.5-.3-.8-.1-.3.2-.3.5-.1.8l2.5 3.5z"/> - <path fill="#E1E1E6" fill-rule="nonzero" d="M179 214.8l-3.8-1.3c-.2-.1-.3 0-.5.1-.1.1-.2.2-.2.3-.1.3.1.6.4.7l3.8 1.3h.2c.2 0 .5-.1.5-.4v-.4c-.1-.2-.2-.3-.4-.3zM179.7 181.2c.1 0 .3 0 .4-.1.2-.2.3-.5.1-.8l-2.7-3.2-1.6-1.9c-.2-.2-.5-.3-.8-.1-.2.2-.3.5-.1.8l.9 1.1 3.3 4c.2.1.4.2.5.2z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M268.1 159.5l-6 5.5c-.2.2-.2.6 0 .8.1.1.3.2.4.2.1 0 .3 0 .4-.1l6-5.5c.2-.2.2-.6 0-.8-.2-.3-.5-.3-.8-.1zM125.9 112.2c.1.2.3.4.5.4h.2c.3-.1.4-.4.3-.7l-1.7-4.7c-.1-.3-.4-.4-.7-.3-.3.1-.4.4-.3.7l1.7 4.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M98.9 188.6c.6-.9 1.6-1.5 2.5-1.7-1.1.2-2.2.8-2.9 1.8-.2.2-.3.5-.4.7h.2c.2-.2.3-.5.6-.8zM113 187.5c-.7-.2-1.3-.4-2-.3-1.6 0-3.3.8-4.3 2.1 0 .1.1.2.1.3 1.4-2 3.9-2.8 6.2-2.1zM95.5 213.7c.3.6.7 1.2 1.3 1.6.5.4 1.1.6 1.7.6l-.1-.1c-.6-.1-1.2-.3-1.7-.7-.6-.4-1-.9-1.2-1.4zM90.5 210.7c-2-1.5-2.9-4-2.4-6.3-.6 2.4.3 5 2.4 6.5 1.1.8 2.4 1.1 3.6 1.1.3 0 .7 0 1-.1v-.2c-1.6.4-3.2.1-4.6-1zM91.6 191.8c.4-.5.8-1 1.3-1.4-.6.4-1.2.9-1.6 1.6-1.8 2.5-1.4 5.9.8 7. [...] - <path fill="#FFF" fill-rule="nonzero" d="M114.5 211.4c.3.1.5.4.5.7-.4 1.9-1 4.2-2.5 5.8l-.1.1c-.5.6-1.2 1.1-2 1.4.9-.4 1.6-.9 2.1-1.5l.1-.1c1.4-1.6 2-3.9 2.4-5.8.1-.3-.1-.6-.5-.7h-.1c-.3 0-.5.2-.6.5v.1c.1-.3.4-.5.7-.5z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M121.5 195.5c.4-1.4.5-2.9.2-4.4-.4-2.7-1.9-5.1-4.2-6.8-1.8-1.3-3.9-2-6.1-2-1.4 0-2.7.3-4 .8-1.4-.9-3.1-1.3-4.8-1.3-2.4 0-4.7.9-6.4 2.5-3.3.1-6.5 1.8-8.4 4.5-1.9 2.7-2.5 6.2-1.6 9.3-.3.3-.6.7-.9 1.1-3.5 4.9-2.4 11.7 2.5 15.2 1.2.8 2.5 1.4 3.8 1.8.6 1 1.4 1.9 2.4 2.6 1.2.9 2.6 1.4 4 1.5.7.6 1.5 1 2.4 1.5l.7 4.2.1.5c.3 2.1 2.2 4.6 6.2 4.6.7 0 4.4-.1 6.5-2.6.5-.6.8-1.2 1-1.9.2-.8.3-1.6.2-2.4l-.3-2.1c.4-.3.8-.7 1.2-1.1l.2-.2c2.2-2.5 3.1-5.7 3.5- [...] - <path fill="url(#w)" fill-rule="nonzero" d="M108.2 214.3c.1 0 .2-.1.3-.1-.1 0-.2 0-.3.1z"/> - <path fill="url(#x)" fill-rule="nonzero" d="M110.3 225.1l-.9-5.4c.1 0 .2-.1.2-.1.2-.1.5-.1.7-.2.8-.3 1.5-.8 2-1.4l.1-.1c1.4-1.6 2.1-3.9 2.5-5.8.1-.3-.1-.6-.5-.7-.3-.1-.6.1-.7.4-.2 1.2-.6 2.6-1.1 3.7v-.1c0 .1 0 .1-.1.2-.2-.5-.4-1-.5-1.4-.1-.2-.1-.3-.2-.4-.1-.3-.4-.5-.7-.4-.1 0-.1.1-.2.1-.1.2-.2.4-.1.6 0 .1.1.3.2.5.2.4.5 1 .6 1.6.2.6.1.9 0 .9l-.1.1c-.4.5-1 .9-1.6 1.1-.2.1-.3.1-.5.2h-.1l-.5-3.3c-.9.3-1.8.4-2.2.4h-.4c-.3 0-.5-.3-.5-.6 0-.1.1-.2.2-.3-.7 0-1.3-.2-2-.4h.1l.5 3s-.1 0-.1-.1c- [...] - <path fill="#EDEDF0" fill-rule="nonzero" d="M107.5 226.5h.4c.7-.1 1.4-.3 1.8-.5-1.2-.1-2.5-.1-3.8-.1v.1c.2.4.8.5 1.6.5z"/> - <path fill="url(#y)" fill-rule="nonzero" d="M107.5 226.5h.4c.7-.1 1.4-.3 1.8-.5-1.2-.1-2.5-.1-3.8-.1v.1c.2.4.8.5 1.6.5z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M105.2 203.6c.8-.3 1.5-.5 2.3-.8-.8.3-1.6.6-2.3.8zM102.8 204.3c-.7.2-1.5.3-2 .5.6-.2 1.3-.3 2-.5zM98.8 214s0 .1 0 0c.2.7.6 1.3.9 1.7h.1c-.5-.5-.8-1-1-1.7zM107.4 202.8c.7-.3 1.4-.6 1.9-.8-.5.2-1.2.5-1.9.8zM99.6 212.7s.1 0 0 0c.1.1.1 0 0 0z"/> - <path fill="#FFF" fill-rule="nonzero" d="M105.8 214.7c.1-.1.3-.2.4-.2 0 0 1 .1 2-.3.1 0 .2-.1.3-.1h.1c.4-.2.8-.5 1.2-.8l.1-.1.4-.4.5-.5c.1-.1.1-.2.2-.3.6-.9 1-2 1.1-3h.6c2 0 4-1 5.3-2.8 1.9-2.7 1.4-6.4-1-8.5-.1-.1-.3-.2-.5-.4-.3-.2-.6-.4-1-.6-.1 0-.1-.1-.2-.1.2-.2.4-.4.5-.6 1.8-2.6 1.2-6.1-1.4-7.9-.4-.3-.9-.5-1.3-.7-2.2-.7-4.8.1-6.2 2.1 0-.1-.1-.2-.1-.3-.1.1-.1.2-.2.2-.3-.8-.9-1.5-1.6-2-.8-.6-1.7-.8-2.7-.8-.3 0-.5.1-.8.1-1 .3-1.9.8-2.5 1.7-.2.3-.4.6-.5.9h-.2c0 .1-.1.1-.1.2-.6-.2-1.2- [...] - <path fill="url(#z)" fill-rule="nonzero" d="M203.6 209.1c.1.2.1.3.1.5.1 0 .2 0 .3.1-.1-.3-.3-.4-.4-.6z"/> - <path fill="url(#A)" fill-rule="nonzero" d="M227 221.9v-1.3-1.4-1.4-.7-1c-.7.3-1.5.5-2.2.5 0 2.6 0 4.6.1 6.2h2.2c-.1-.3-.1-.6-.1-.9z"/> - <path fill="url(#B)" fill-rule="nonzero" d="M203.3 223.1l-.2-.2-.5-.5c-.1-.1-.2-.2-.2-.3-.1-.2-.3-.3-.4-.5-.1-.1-.2-.2-.2-.3-.2-.2-.3-.4-.5-.5-.1-.1-.2-.2-.2-.3l-.6-.6c-.1-.1-.1-.2-.2-.2-.3-.3-.5-.6-.8-.9-.5-.6-1-1.2-1.5-1.9l-.1-.1-1.2-1.8c0-.1-.1-.1-.1-.2-.4-.6-.7-1.2-1.1-1.9 0-.1-.1-.1-.1-.2-.3-.6-.7-1.3-1-1.9 0-.1 0-.1-.1-.2-.3-.6-.5-1.1-.7-1.7-1-.4-2-.9-3-1.4 1.6 4.2 3.9 8.9 7.1 12.7 1.1 1.3 2 2.3 2.9 3.3.9-.1 1.9-.1 2.8-.2 0 0 0-.1-.1-.2z"/> - <path fill="url(#C)" fill-rule="nonzero" d="M204.7 212.6c0 .1 0 .1.1.2.1.4.2.8.4 1.2v.1c.1.4.2.7.3 1.1 0 .1.1.2.1.3.1.4.2.7.3 1.1v.1l.3 1.2c0 .1.1.2.1.3.1.3.2.6.2.9 0 .1.1.2.1.3.1.4.2.7.3 1.1 0 .1 0 .1.1.2.1.3.1.6.2.8 0 .1 0 .2.1.3.1.3.1.6.2.9V223c.7 0 1.5-.1 2.3-.1-.4-2.1-1.3-5.4-2.9-10.6-.8-.1-1.6-.3-2.5-.4.1.3.2.5.3.7z"/> - <path fill="url(#D)" fill-rule="nonzero" d="M214.9 199.4l8.1 2.3h.2c.4 0 .7-.2.8-.6.1-.4-.1-.9-.6-1l-8.1-2.3h-.2c-.4 0-.7.2-.8.6-.1.4.1.8.6 1z"/> - <path fill="url(#E)" fill-rule="nonzero" d="M267.5 198.4l-1.2-.6c-.4.3-.9.6-1.3.9.9-.1 1.7-.2 2.5-.3z"/> - <path fill="url(#F)" fill-rule="nonzero" d="M151.3 155.4c-1.2-.4-2.4-.6-3.6-.6-2.5 0-4.9.9-6.8 2.5l1.2-3.3c.1-.3 0-.7-.4-.8h-.2c-.3 0-.5.2-.6.4l-1.1 2.8v4.4l5.2 2h.2c.3 0 .5-.2.6-.4.1-.3 0-.7-.4-.8l-3.7-1.4c1.5-1.8 3.7-2.7 5.8-2.7 1.8 0 3.6.6 5.1 1.9 3.2 2.8 3.6 7.7.7 10.9-1.5 1.8-3.7 2.7-5.8 2.7-1.8 0-3.6-.6-5.1-1.9-1.7-1.5-2.7-3.6-2.7-5.9v2.5c0 1.1-.3 2.2-.9 3 1.9 2.8 5 4.6 8.6 4.6h.1c5.7-.1 10.3-4.7 10.2-10.4.2-4.2-2.4-8-6.4-9.5z"/> - <path fill="url(#G)" fill-rule="nonzero" d="M144.1 167.6l.8.3.1.2 3.3-1.5c.2-.1.3-.3.3-.4l1.8-4.8c.1-.3 0-.7-.4-.8h-.2c-.3 0-.5.2-.6.4l-1.7 4.6-3.1 1.3c-.3 0-.4.4-.3.7z"/> - <path fill="url(#H)" fill-rule="nonzero" d="M163 112.5c.1.5.7.5.8 0 1.2-4.8 5-8.6 9.8-9.8.5-.1.5-.7 0-.8-4.8-1.2-8.6-5-9.8-9.8-.1-.5-.7-.5-.8 0-1.2 4.8-5 8.6-9.8 9.8-.5.1-.5.7 0 .8 4.8 1.2 8.5 5 9.8 9.8z"/> - <path fill="url(#I)" fill-rule="nonzero" d="M234.8 212.7c-.1.5-.2 1.1-.3 1.6v.1c-.1.5-.2 1-.4 1.5-.1.5-.3.9-.4 1.4-.1.4-.3.8-.4 1.1 0 .1-.1.3-.1.4-.1.2-.2.5-.3.7-.1.1-.1.3-.2.4-.1.2-.2.4-.2.6-.1.1-.1.3-.2.4-.1.2-.2.4-.2.5-.1.1-.1.3-.2.4-.1.2-.2.3-.2.5-.1.1-.1.2-.2.3 0 0 0 .1-.1.1.8 0 1.7 0 2.5.1.8-1.7 1.5-3.5 2-4.9.6-1.7 1.1-4 1.6-6.9l-2.4.6c-.1.3-.1.6-.2 1l-.1.1z"/> - <path fill="url(#J)" fill-rule="nonzero" d="M191.9 204.4l-.3-.9c1.4 1.1 3.3 2 6 2.5h.1c.3 0 .5-.2.5-.5.1-.3-.1-.6-.4-.6-5.2-1-7.3-3.8-8.2-5.4-.1-.4-.3-.8-.4-1.2 0-.1-.1-.3-.1-.5l-.3-1.2c0-.2-.1-.4-.1-.5-.1-.4-.1-.8-.2-1.2v-.6-1c-.1.1-.2.1-.3.1-.1 0-.2 0-.3-.1-.5-.4-1.1-.7-1.6-1.1-.1 4.4.9 8.4 3 11.4l.3.9c1 .6 1.9 1.1 2.9 1.6-.3-.6-.4-1.2-.6-1.7z"/> - <path fill="url(#K)" fill-rule="nonzero" d="M178.1 85.3c-.1-.3-.5-.3-.6 0-.8 3.2-3.4 5.8-6.6 6.6-.3.1-.3.5 0 .6 3.2.8 5.8 3.4 6.6 6.6.1.3.5.3.6 0 .8-3.2 3.4-5.8 6.6-6.6.3-.1.3-.5 0-.6-3.3-.9-5.8-3.4-6.6-6.6z"/> - <path fill="url(#L)" fill-rule="nonzero" d="M180.4 204.7l3.1-.9-.9-3-3.1.9"/> - <path fill="url(#M)" fill-rule="nonzero" d="M176.8 200.9c-.9 0-1.9.4-2.5 1.2l-4.6 6.2-3.6-1.3-.1-.5c-.4-1.2-1.3-2.2-2.5-2.6l-.7-2.3-3.1.9.5 1.7c-2 1-2.8 3.4-1.8 5.4.7 1.4 2.1 2.2 3.6 2.2.6 0 1.2-.1 1.8-.4.6-.3 1.2-.8 1.5-1.4l2.3.8-1.7 2.2c-.3-.1-.7-.1-1-.1-1.8 0-3.4 1.2-3.9 3-.6 2.1.7 4.3 2.9 4.9.3.1.7.1 1 .1 1.8 0 3.4-1.2 3.9-3 .2-.7.2-1.5-.1-2.2-.1-.3-.1-.5-.2-.8l10.3-13.5c-.6-.3-1.3-.5-2-.5zm-13.9 8.8c-.1 0-.1 0-.2.1-.2.1-.5.1-.8.1-.8 0-1.6-.5-1.9-1.3-.4-1.1.1-2.3 1.2-2.7.2-.1.5-. [...] - <path fill="url(#N)" fill-rule="nonzero" d="M274.7 179.9c.4-1.1 1.2-2 2.3-2.4-.4-.2-.8-.3-1.2-.4-.3-.1-.6-.1-.9-.2-.2 1-.4 1.9-.8 3h.6z"/> - <path fill="url(#O)" fill-rule="nonzero" d="M180.9 206.3l.9 3.1c1.7-.5 2.6-2.3 2.1-4l-3 .9z"/> - <path fill="url(#P)" fill-rule="nonzero" d="M284.7 187.9c-.4-.2-.7-.3-1-.3-.7 0-1.3.3-1.6.9-1.7 3.1-4.9 4.9-8.3 4.9-.8 0-1.5-.1-2.3-.3-2.9-.7-5.3-2.8-6.4-5.6l3.7 1c.2 0 .3.1.5.1.8 0 1.6-.6 1.8-1.4.3-1-.3-2-1.4-2.3l-7.3-1.9c-.2 0-.3-.1-.5-.1-.8 0-1.6.6-1.8 1.4l-1.9 7.3c-.3 1 .3 2 1.4 2.3.2 0 .3.1.5.1.8 0 1.6-.6 1.8-1.4l.5-2c2.4 4.4 6.9 6.9 11.5 6.9 2.1 0 4.2-.5 6.1-1.5 2.3-1.3 4.3-3.2 5.5-5.6.5-.9.2-2-.8-2.5z"/> - <path fill="url(#Q)" fill-rule="nonzero" d="M172.7 212.8l2.1.8c.1-.1.3-.1.5-.1l3.8 1.3c.2 0 .3.2.3.3 1.3 0 2.6-.9 3-2.2l-7.7-2.7-2 2.6z"/> - <path fill="url(#R)" fill-rule="nonzero" d="M176.1 196l-.9-3-3.1.9 1 3"/> - <path fill="url(#S)" fill-rule="nonzero" d="M171.5 197.4l-.9-3.1-3.1 1 1 3"/> - <path fill="url(#T)" fill-rule="nonzero" d="M166.9 198.8l-.9-3.1-3.1 1 1 3"/> - <path fill="url(#U)" fill-rule="nonzero" d="M162.3 200.2l-.9-3.1c-1.7.5-2.6 2.3-2.1 4l3-.9z"/> - <path fill="url(#V)" fill-rule="nonzero" d="M274.7 180c-.2 0-.4-.1-.6-.1-.4 1.3-1 2.7-1.8 4.1.9 1 1.3 2.4 1 3.8-.3 1.3-1.3 2.3-2.5 2.8l.9.3c3-2.5 5.1-4.5 6.1-5.5l-.3-.1c-1.1-.3-2-1-2.5-1.9-.6-1-.7-2.1-.4-3.1 0-.1.1-.2.1-.3z"/> - <path fill="url(#W)" fill-rule="nonzero" d="M274.9 191.2c2.2-.3 4.2-1.7 5.3-3.7v-.1c.3-.4.6-.8 1-1.1l-1-.3s0 .1-.1.1c0 .2-1.9 2.2-5.2 5.1z"/> - <path fill="url(#X)" fill-rule="nonzero" d="M187.6 158.4c-1.4-.9-3.2-.6-4 .7-.8 1.3-.3 3 1.1 3.9 1.4.9 3.2.6 4-.7.8-1.2.3-3-1.1-3.9z"/> - <path fill="url(#Y)" fill-rule="nonzero" d="M276.7 136.6c-.3.7-.4 1.4-.4 2.1l-.9.3c-1.2.5-2.5 1.1-3.7 1.8-.2.1-.4.3-.7.4-.4.2-.7.5-1.2.8-.2.1-.4.3-.6.4l-.6.5c-.1.1-.3.2-.4.3h-.8c-1.1.1-9.8 2-18 3.9.6-1.9 1.2-3.8 1.6-5.8 1.3 0 2.7-.1 4-.2 1 .9 2.3 1.3 3.7 1.3 2.1 0 3.9-1.1 4.9-2.7.5.1 1 .1 1.5.1 4.8 0 9.1-3 10.7-7.5 3-1 5.2-3.7 5.7-6.9.6-.9 1.2-1.8 1.8-2.8 4-1.5 6.6-5.7 6-10 0-.4-.1-.7-.2-1.1 1.5-1.9 2.1-4.4 1.8-6.8-.3-2.1-1.2-4.1-2.7-5.6-.3-2.4-.8-4.7-1.5-7 .1-.4.1-.9.1-1.3 0-3.3-1.7 [...] - <path fill="#FFF" fill-rule="nonzero" d="M219.1 84.8c0-.6.1-1.3.3-1.8-.2.5-.3 1.2-.3 1.8z"/> - <path fill="url(#Z)" fill-rule="nonzero" d="M219.1 84.8c0-.6.1-1.3.3-1.8-.2.5-.3 1.2-.3 1.8z"/> - <path fill="url(#aa)" fill-rule="nonzero" d="M219.1 84.8c0-.6.1-1.3.3-1.8-.2.5-.3 1.2-.3 1.8z"/> - <path fill="url(#ab)" fill-rule="nonzero" d="M178.6 177.5c-.4-.2-.8-.3-1.1-.4l2.7 3.2c.2.2.2.6-.1.8-.1.1-.2.1-.4.1s-.3-.1-.4-.2l-3.3-4c-.9.1-1.6.6-2 1.3-.2.3-.3.7-.3 1.1 1.2 1.3 2.4 2.6 3.6 3.7 1.4.3 2.7-.2 3.3-1.2.7-1.4-.2-3.4-2-4.4z"/> - <path fill="url(#ac)" fill-rule="nonzero" d="M267.8 172.1c-2.3 1.3-4.3 3.2-5.5 5.6-.5.9-.1 2.1.8 2.5h.1l.4.1c.2 0 .3.1.5.1.7 0 1.4-.4 1.7-1.1 1.7-3 4.9-4.8 8.2-4.8.8 0 1.6.1 2.4.3 2.9.7 5.3 2.8 6.4 5.6l-3.7-1c-.2 0-.3-.1-.5-.1-.8 0-1.6.6-1.8 1.4-.3 1 .3 2 1.4 2.3l7.3 1.9c.2 0 .3.1.5.1.8 0 1.6-.6 1.8-1.4l1.9-7.3c.3-1-.3-2-1.4-2.3-.2 0-.3-.1-.5-.1-.8 0-1.6.6-1.8 1.4l-.5 2c-2.4-4.4-6.9-6.9-11.5-6.9-2.2.2-4.3.7-6.2 1.7z"/> - <path fill="url(#ad)" fill-rule="nonzero" d="M180.7 194.6c-.4-1.4-1.7-2.3-3.1-2.3-.3 0-.6 0-.9.1l.9 3.1 3.1-.9z"/> - <path fill="url(#ae)" fill-rule="nonzero" d="M172.6 172.1c.8-1.4.2-3.2-1.3-4-1.5-.8-3.3-.3-4.1 1.1-.8 1.4-.2 3.2 1.3 4 1.5.8 3.3.3 4.1-1.1z"/> - <path fill="url(#af)" fill-rule="nonzero" d="M175.8 164.2c1.7 0 3.1-1.3 3.1-2.9 0-1.6-1.4-2.9-3.1-2.9-1.7 0-3.1 1.3-3.1 2.9 0 1.6 1.4 2.9 3.1 2.9z"/> - <path fill="url(#ag)" fill-rule="nonzero" d="M224.1 117.1c.4 0 .9.1 1.3.1 0-.1.1-.2.1-.3v-3c0-.7-.6-1.3-1.3-1.3-.7 0-1.3.6-1.3 1.3v3c0 .1 0 .2.1.3.2-.1.7-.1 1.1-.1z"/> - <path fill="url(#ah)" fill-rule="nonzero" d="M237.5 199.2c.6-7.2 1.2-16.6 1.3-16.7 0-.3-.2-.6-.5-.6s-.6.2-.6.5c0 .2-1.4 19.4-1.9 23.9v.3c0 .2-.1.5-.1.7v.1c-.1.6-.2 1.1-.2 1.7v.2c.8-.2 1.6-.4 2.3-.6.1-.9.3-1.8.4-2.8 5.1-.6 12.8-2.1 20-5.6 2.3-1.3 4.3-2.6 6.2-3.9-.5-.4-1-.8-1.5-1.3-.8.7-1.8 1.2-2.9 1.2-.3 0-.7 0-1-.1-1.4.9-3 1.8-4.7 2.6-6.6 3-13 4.1-17.1 4.5.1-.9.2-1.8.2-2.9 1.8-.1 3.6-.2 5.5-.5 5.9-.9 10.7-2.9 14.2-4.8-.2-.2-.4-.5-.6-.8 0 0 0-.1-.1-.2-3.4 1.9-8 3.8-13.7 4.7-1.8.2-3.5. [...] - <path fill="url(#ai)" fill-rule="nonzero" d="M264.9 127.4c1 0 2.1-.3 3.3-1.3 2.4-2 2.5-4.3 2.3-5.6 1.4-.2 2.8-.8 4.2-2.2 3.6-3.7 2.9-7.8 2.1-9.4-.3-.5-1-.8-1.5-.5-.5.3-.8 1-.5 1.5 0 0 1.7 3.4-1.7 6.8-2.9 2.9-5.8 1.1-6.1.9-.5-.4-1.2-.2-1.6.3-.4.5-.2 1.2.3 1.6.8.5 2.1 1.1 3.7 1.2.2.9.2 2.9-1.9 4.7-2.6 2.1-4.8.3-4.9.2-.2-.2-.6-.2-.8.1-.2.2-.2.6.1.8 0-.2 1.2.9 3 .9z"/> - <path fill="url(#aj)" fill-rule="nonzero" d="M249.6 126.6c1 1 2.7 2.2 5.8 2.2.8 0 1.7-.1 2.7-.3.3-.1.5-.3.4-.6-.1-.3-.3-.5-.6-.4-4.9.9-6.7-1-7.4-1.7-1.3-1.3-1.9-4.5-1.9-4.6 0-.2-.2-.4-.4-.4-.1 0-5.1-1.5-8.5-5.7-3.1-3.9-3.5-8.4-3.5-8.4 0-.2-.1-.4-.3-.5-.8-.4-3.2-1.7-3.8-3.2-1.3-3.1.1-4.9.1-4.9.2-.2.2-.6-.1-.8-.2-.2-.6-.2-.8.1 0 0-.5.6-.8 1.7l-3.3-.1c-.3 0-.6.2-.6.5v.2c.1.2.3.4.5.4l3.2.1c0 .9.1 2 .6 3.2.4.9 1.2 1.7 2 2.3.8.6 1.6 1.1 2.1 1.3 0 .3.1.8.3 1.4.4 1.8 1.3 4.7 3.5 7.3.4.5.8 1 [...] - <path fill="url(#ak)" fill-rule="nonzero" d="M179 200.2l3.1-1-.9-3-3.1.9"/> - <path fill="url(#al)" fill-rule="nonzero" d="M231.2 188.3l-16.2-4.7c-.3-.1-.6-.1-.9-.1-1.5 0-2.8 1-3.2 2.4l-5.6 19.4c-.5 1.8.5 3.7 2.3 4.2l16.2 4.7c.3.1.6.1.9.1 1.5 0 2.8-1 3.2-2.4l5.6-19.4c.5-1.8-.5-3.7-2.3-4.2zm-1.4 4.9l-4.7 16.2c-.2.7-.9 1.2-1.6 1.2-.2 0-.3 0-.5-.1l-12.9-3.7c-.9-.3-1.4-1.2-1.2-2.1l4.7-16.2c.2-.7.9-1.2 1.6-1.2.2 0 .3 0 .5.1l12.9 3.7c.9.2 1.5 1.2 1.2 2.1z"/> - <path fill="url(#am)" fill-rule="nonzero" d="M99.3 199.1c-.2-.6-.9-1-1.5-.7-.6.2-1 .9-.7 1.5l.8 2.4c.6-.6 1.4-.9 2.2-.8l-.8-2.4z"/> - <path fill="url(#an)" fill-rule="nonzero" d="M224.4 196.8l-8.1-2.3h-.2c-.4 0-.7.2-.8.6-.1.4.1.9.6 1l8.1 2.3h.2c.4 0 .7-.2.8-.6.1-.4-.2-.8-.6-1z"/> - <path fill="url(#ao)" fill-rule="nonzero" d="M108 198.9c.6-.6 1.4-.9 2.2-.8l-.8-2.4c-.2-.6-.9-1-1.5-.7-.6.2-1 .9-.7 1.5l.8 2.4z"/> - <path fill="url(#ap)" fill-rule="nonzero" d="M106.2 206.7c1-.5 2-1 3.1-.7.1-.1.3-.2.4-.4.5-.5.9-1.1 1.3-1.8.2-.4.4-.7.6-1.1.2-.4.3-.9.5-1.4l.1-.2v-.1c0-.1-.1-.2-.2-.1-.2.1-.5.1-.7.2-.3.1-.7.2-1 .3l-1.7.5c-1.2.3-2.3.7-3.5 1.1-1.1.4-2.3.8-3.4 1.2l-1.7.6c-.3.1-.6.3-1 .4-.2.1-.5.2-.7.3 0 0-.1 0-.1.1-.1.1 0 .2 0 .3l.2.1c.4.3.8.6 1.2.8.4.2.8.4 1.1.6.7.3 1.4.5 2.1.6.2 0 .4 0 .5.1.1-.1.1-.2.2-.2.7-.9 1.7-1 2.7-1.2zm-5.4-1.9c.6-.1 1.3-.3 2-.5-.7.2-1.4.3-2 .5zm6.6-2c.7-.3 1.4-.6 1.9-.8-.5.2-1. [...] - <path fill="url(#aq)" fill-rule="nonzero" d="M225.3 193.6l-8.1-2.3h-.2c-.4 0-.7.2-.8.6-.1.4.1.9.6 1l8.1 2.3h.2c.4 0 .7-.2.8-.6.1-.4-.2-.9-.6-1z"/> - <path fill="url(#ar)" fill-rule="nonzero" d="M164 84.3c-.2 0-.2.3 0 .3 1.8.5 3.3 1.9 3.7 3.7 0 .2.3.2.3 0 .5-1.8 1.9-3.3 3.7-3.7.2 0 .2-.3 0-.3-1.6-.4-2.9-1.6-3.5-3.1h-.7c-.6 1.5-1.9 2.7-3.5 3.1z"/> - <path fill="url(#as)" fill-rule="nonzero" d="M213.9 202.6l3.2.9h.2c.4 0 .7-.2.8-.6.1-.4-.1-.9-.6-1l-3.2-.9h-.2c-.4 0-.7.2-.8.6-.1.4.2.9.6 1z"/> - <path fill="url(#at)" fill-rule="nonzero" d="M227.9 119.2l-.3-.3c-.1-.1-.2-.2-.3-.2-.7-.5-1.8-1.1-3.2-1.1-2.9 0-4.4 2.2-4.5 2.3-.3.5-.2 1.2.3 1.5.5.3 1.2.2 1.5-.3 0 0 .9-1.3 2.6-1.3 1.1 0 1.9.5 2.3.9l.2.2.2.2c.2.3.6.5.9.5.2 0 .4-.1.6-.2.5-.3.7-1 .3-1.5.1 0-.1-.3-.6-.7z"/> - <path fill="url(#au)" fill-rule="nonzero" d="M196.8 121.5c.5.3 1.2.2 1.5-.3 0 0 .1-.2.4-.4.4-.4 1.2-.9 2.2-.9 1.7 0 2.6 1.3 2.6 1.3.2.3.6.5.9.5.2 0 .4-.1.6-.2.5-.3.7-1 .3-1.5-.1-.1-1.5-2.3-4.5-2.3-1.9 0-3.2 1-3.9 1.7l-.3.3c-.2.2-.3.4-.3.4-.2.4 0 1.1.5 1.4z"/> - <path fill="url(#av)" fill-rule="nonzero" d="M200.9 117.1c.4 0 .9.1 1.3.1 0-.1.1-.2.1-.3v-3c0-.7-.6-1.3-1.3-1.3-.7 0-1.3.6-1.3 1.3v3c0 .1 0 .2.1.3.3-.1.7-.1 1.1-.1z"/> - <path fill="url(#aw)" fill-rule="nonzero" d="M207.7 137.3l-1.5-.6c-.7-.3-1.5-.5-2.2-.8l-3.8-1.3-7.5-2.4c-.2-.1-.4-.1-.6-.2-1.1 1.2-2.3 2.4-2.8 2.7-.4.2-3.4-.5-3.5-.8-.1-.2.3-1.9.7-3.5-.5-.1-.9-.3-1.4-.4l-.6-.1c-.3 1.4-.7 3.1-.9 3.2-.4.3-2.9-1.2-3.1-1.5-.1-.2-.5-1.6-.7-2.9-.3-.1-.5-.1-.8-.2-.5-.1-1.1-.2-1.6-.4h-.2c-.2.1-.3.3-.3.5l.1.5c.3 1.1.7 2.2 1.2 3 .4.9.9 1.7 1.3 2.5.9 1.5 1.9 2.7 3 3.8.3.3.6.5.9.8.2-.1.4-.1.6-.1-.2 0-.4.1-.6.1 1.9 1.6 3.9 2.7 6 3.3h.2c2.2.6 4.4.8 6.9.5.4 0 .8-.1 [...] - <path fill="url(#ax)" fill-rule="nonzero" d="M231.2 80.2c.4 0 .6-.2.6-.5 0-.1.5-3 4.2-3.5 1.8-.3 2.9.5 3.5 1.2-3.5 2.2-4.1 5.7-3.9 7.3.1.6.6 1 1.1 1h.1c.6-.1 1-.6 1-1.2 0 0-.4-3.8 4-5.8 3.8-1.7 5.8 1 6.1 1.4.3.5 1 .6 1.5.3s.6-1 .3-1.5c-.5-.7-1.3-1.5-2.5-2.1.5-.9 1.6-2.1 3.8-2.4 3.3-.5 4.2 2.3 4.3 2.4.1.3.4.5.7.4.3-.1.5-.4.4-.7 0 0-1.3-3.8-5.5-3.2-2.8.4-4.1 2-4.7 3.1-1.5-.5-3.3-.5-5.3.4-.1.1-.3.1-.4.2-.8-1-2.2-2.1-4.7-1.7-4.5.6-5.1 4.4-5.2 4.4 0 .2.3.5.6.5z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M207.7 223.7v.2c0 .2.1.4.1.6v.2c0 .2.1.4.1.6v.5c0 .1 0 .2-.1.2l-.2.2H207.3h-.1-.1c-.1 0-.1 0-.2-.1h-.1c-.1 0-.2-.1-.3-.2 0 0-.1 0-.1-.1-.1-.1-.2-.1-.3-.2l-.1-.1c-.1-.1-.2-.1-.3-.2-.1 0-.1-.1-.2-.1l-.3-.3-.2-.2-.3-.3-.2-.2-.5-.5-.1-.1-.4-.4c-1 .1-1.9.1-2.8.2 3.3 3.6 5 4.9 6.6 4.9.9 0 1.8-.4 2.3-1.2.4-.6.8-1.1.2-4.3-.8 0-1.5.1-2.3.1.1.4.1.6.2.8z"/> - <path fill="url(#ay)" fill-rule="nonzero" d="M207.7 223.7v.2c0 .2.1.4.1.6v.2c0 .2.1.4.1.6v.5c0 .1 0 .2-.1.2l-.2.2H207.3h-.1-.1c-.1 0-.1 0-.2-.1h-.1c-.1 0-.2-.1-.3-.2 0 0-.1 0-.1-.1-.1-.1-.2-.1-.3-.2l-.1-.1c-.1-.1-.2-.1-.3-.2-.1 0-.1-.1-.2-.1l-.3-.3-.2-.2-.3-.3-.2-.2-.5-.5-.1-.1-.4-.4c-1 .1-1.9.1-2.8.2 3.3 3.6 5 4.9 6.6 4.9.9 0 1.8-.4 2.3-1.2.4-.6.8-1.1.2-4.3-.8 0-1.5.1-2.3.1.1.4.1.6.2.8z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M231.2 223.1c-.1.1-.1.2-.2.3-.1.2-.2.3-.3.4 0 .1-.1.2-.1.2-.1.2-.2.4-.4.5v.1c-.1.2-.3.4-.4.5 0 .1-.1.1-.1.1-.1.1-.2.2-.2.3 0 .1-.1.1-.1.1l-.2.2-.1.1c-.1.1-.1.1-.2.1l-.1.1c-.1 0-.1.1-.2.1 0 0-.1 0-.1.1h-.2-.1-.1-.1c-.1 0-.2-.1-.2-.1l-.1-.1c-.1-.1-.1-.3-.2-.6s-.1-.6-.1-1c0-.3-.1-.7-.1-1.1v-.9h-2.2c.2 4.5.8 4.9 1.4 5.4.5.4 1.2.6 1.8.6 2.3 0 4.3-2.8 5.8-5.9-.8 0-1.6 0-2.5-.1-.3.4-.4.5-.4.6z"/> - <path fill="url(#az)" fill-rule="nonzero" d="M231.2 223.1c-.1.1-.1.2-.2.3-.1.2-.2.3-.3.4 0 .1-.1.2-.1.2-.1.2-.2.4-.4.5v.1c-.1.2-.3.4-.4.5 0 .1-.1.1-.1.1-.1.1-.2.2-.2.3 0 .1-.1.1-.1.1l-.2.2-.1.1c-.1.1-.1.1-.2.1l-.1.1c-.1 0-.1.1-.2.1 0 0-.1 0-.1.1h-.2-.1-.1-.1c-.1 0-.2-.1-.2-.1l-.1-.1c-.1-.1-.1-.3-.2-.6s-.1-.6-.1-1c0-.3-.1-.7-.1-1.1v-.9h-2.2c.2 4.5.8 4.9 1.4 5.4.5.4 1.2.6 1.8.6 2.3 0 4.3-2.8 5.8-5.9-.8 0-1.6 0-2.5-.1-.3.4-.4.5-.4.6z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M167.7 80.6c-.1.2-.1.4-.2.6h.7c-.1-.2-.2-.4-.2-.6 0-.2-.3-.2-.3 0z"/> - <path fill="url(#aA)" fill-rule="nonzero" d="M167.7 80.6c-.1.2-.1.4-.2.6h.7c-.1-.2-.2-.4-.2-.6 0-.2-.3-.2-.3 0z"/> - <path fill="#FFF" fill-rule="nonzero" d="M118.4 58.7c.1.2.2.3.4.3h.1c.3-.1.4-.3.4-.6v-.1l-1.9-5.3c-.1-.3-.4-.4-.6-.3-.3.1-.4.4-.3.6l1.9 5.4z"/> - <path fill="url(#aB)" fill-rule="nonzero" d="M118.4 58.7c.1.2.2.3.4.3h.1c.3-.1.4-.3.4-.6v-.1l-1.9-5.3c-.1-.3-.4-.4-.6-.3-.3.1-.4.4-.3.6l1.9 5.4z"/> - <path fill="#FFF" fill-rule="nonzero" d="M134.3 121.9c.2-.2.5-.4.9-.2v.1l2.4-1.5v-3.5l-3.4 2.1v3h.1zM137.7 164.3c-.2.2-.4.6-.4.9 0 .9.1 1.8.4 2.6v-3.5z"/> - <path fill="url(#aC)" fill-rule="nonzero" d="M137.7 164.3c-.2.2-.4.6-.4.9 0 .9.1 1.8.4 2.6v-3.5z"/> - <path fill="#FFF" fill-rule="nonzero" d="M115.5 59c.3 0 .5-.2.5-.5v-5.3c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.3c0 .3.2.5.5.5z"/> - <path fill="url(#aD)" fill-rule="nonzero" d="M115.5 59c.3 0 .5-.2.5-.5v-5.3c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.3c0 .3.2.5.5.5z"/> - <path fill="#FFF" fill-rule="nonzero" d="M112.6 59c.3 0 .5-.2.5-.5v-5.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.8c0 .3.2.5.5.5z"/> - <path fill="url(#aE)" fill-rule="nonzero" d="M112.6 59c.3 0 .5-.2.5-.5v-5.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v5.8c0 .3.2.5.5.5z"/> - <path fill="#FFF" fill-rule="nonzero" d="M114 59c.3 0 .5-.2.5-.5v-4.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v4.8c0 .3.3.5.5.5z"/> - <path fill="url(#aF)" fill-rule="nonzero" d="M114 59c.3 0 .5-.2.5-.5v-4.8c0-.3-.2-.5-.5-.5s-.5.2-.5.5v4.8c0 .3.3.5.5.5z"/> - <path fill="#FFF" fill-rule="nonzero" d="M129.1 125.6l4.1-2.6v-.5c0-.1-.1-.1-.1-.2 0 0 .1 0 .1.1v-2.9l-5.6 3.6c-.8.3-1.1 1.2-.8 2 .2.6.8.9 1.3.9.2 0 .4 0 .6-.1.1-.1.2-.2.4-.3z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M139 115.8l-1.3.9v3.5l2.2-1.4v-8.3c-.5.7-.9 1.5-1.1 2.3-.1.5-.1 1-.1 1.4.1.6.1 1.1.3 1.6zM139.9 165.2c0-.7-.6-1.3-1.3-1.3-.4 0-.7.1-.9.4v3.5c.3 1.1.7 2.1 1.3 3 .6-.9.9-1.9.9-3v-2.6z"/> - <path fill="url(#aG)" fill-rule="nonzero" d="M139.9 165.2c0-.7-.6-1.3-1.3-1.3-.4 0-.7.1-.9.4v3.5c.3 1.1.7 2.1 1.3 3 .6-.9.9-1.9.9-3v-2.6z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M138.7 159.7c-.1.3 0 .7.4.8l.8.3v-4.4l-1.2 3.3z"/> - <path fill="url(#aH)" fill-rule="nonzero" d="M138.7 159.7c-.1.3 0 .7.4.8l.8.3v-4.4l-1.2 3.3z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M198 217c.5.6.9 1.3 1.5 1.9-.5-.7-1-1.3-1.5-1.9z"/> - <path fill="url(#aI)" fill-rule="nonzero" d="M198 217c.5.6.9 1.3 1.5 1.9-.5-.7-1-1.3-1.5-1.9z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M207.8 226l-.2.2c.1-.1.2-.1.2-.2z"/> - <path fill="url(#aJ)" fill-rule="nonzero" d="M207.8 226l-.2.2c.1-.1.2-.1.2-.2z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M224.1 117.6c1.4 0 2.5.5 3.2 1.1-.7-.5-1.8-1.1-3.2-1.1z"/> - <path fill="url(#aK)" fill-rule="nonzero" d="M224.1 117.6c1.4 0 2.5.5 3.2 1.1-.7-.5-1.8-1.1-3.2-1.1z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M224.1 119.9c1.1 0 1.9.5 2.3.9-.4-.4-1.2-.9-2.3-.9z"/> - <path fill="url(#aL)" fill-rule="nonzero" d="M224.1 119.9c1.1 0 1.9.5 2.3.9-.4-.4-1.2-.9-2.3-.9z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M134.3 121.9v-3.1l-1.1.7v2.9s-.1 0-.1-.1c0 .1.1.1.1.2v.5l2.1-1.3v-.1c-.5-.1-.8 0-1 .3z"/> - <path fill="url(#aM)" fill-rule="nonzero" d="M150.7 112.6l-4.5 2.7c-.3.2-.7.3-1 .3-.6 0-1.3-.3-1.6-.9-.4-.6-.4-1.3-.1-1.8.1-.4.4-.7.8-1l4.2-2.7c-.7-.5-1.5-.9-2.3-1.1-.4-.1-.8-.1-1.3-.1-2 0-3.8 1-4.9 2.5-.5.7-.9 1.5-1.1 2.3-.1.5-.1 1-.1 1.4 0 .5.1 1 .3 1.5v.1l-1.3.8-3.4 2.1-1.1.7-5.6 3.6c-.8.3-1.1 1.2-.8 2 .2.6.8.9 1.3.9.2 0 .4 0 .6-.1.1-.1.3-.1.4-.2l4.1-2.6 2.1-1.3 2.4-1.5 2.2-1.4.7-.5c.8.8 1.7 1.3 2.8 1.6.5.1.9.2 1.4.2 1.4 0 2.7-.5 3.7-1.3s1.8-2 2.1-3.4c.2-1 .2-1.9 0-2.8z"/> - <path fill="#FFFEFE" fill-rule="nonzero" d="M143.5 114.7c.4.6 1 .9 1.6.9.4 0 .7-.1 1-.3l4.5-2.7c.2.9.2 1.9 0 2.8-.3 1.4-1.1 2.6-2.1 3.4 1.1-.8 1.9-2.1 2.2-3.5.2-.9.2-1.9 0-2.8l-4.6 2.7c-.3.2-.7.3-1 .3-.6 0-1.3-.3-1.7-.9-.3-.5-.4-1.2-.2-1.7-.1.5-.1 1.2.3 1.8z"/> - <path fill="url(#aN)" fill-rule="nonzero" d="M143.5 114.7c.4.6 1 .9 1.6.9.4 0 .7-.1 1-.3l4.5-2.7c.2.9.2 1.9 0 2.8-.3 1.4-1.1 2.6-2.1 3.4 1.1-.8 1.9-2.1 2.2-3.5.2-.9.2-1.9 0-2.8l-4.6 2.7c-.3.2-.7.3-1 .3-.6 0-1.3-.3-1.7-.9-.3-.5-.4-1.2-.2-1.7-.1.5-.1 1.2.3 1.8z"/> - <path fill="#FFFEFE" fill-rule="nonzero" d="M126.8 125.1c-.3-.8 0-1.7.8-2l5.6-3.6 1.1-.7 3.4-2.1 1.3-.8v-.1l-11.5 7.3c-.8.3-1.1 1.3-.8 2 .3.6.8.9 1.4.9h.1c-.7 0-1.2-.3-1.4-.9z"/> - <path fill="url(#aO)" fill-rule="nonzero" d="M126.8 125.1c-.3-.8 0-1.7.8-2l5.6-3.6 1.1-.7 3.4-2.1 1.3-.8v-.1l-11.5 7.3c-.8.3-1.1 1.3-.8 2 .3.6.8.9 1.4.9h.1c-.7 0-1.2-.3-1.4-.9z"/> - <path fill="#FFFEFE" fill-rule="nonzero" d="M139.9 110.5c1.1-1.5 2.9-2.5 4.9-2.5.4 0 .8 0 1.3.1.8.2 1.6.6 2.3 1.1l.2-.1c-.7-.6-1.5-1-2.4-1.2-.4-.1-.8-.1-1.3-.1-2.8 0-5.4 2-6 4.9-.1.5-.1 1-.1 1.6 0-.5 0-1 .1-1.4.1-1 .5-1.7 1-2.4z"/> - <path fill="url(#aP)" fill-rule="nonzero" d="M139.9 110.5c1.1-1.5 2.9-2.5 4.9-2.5.4 0 .8 0 1.3.1.8.2 1.6.6 2.3 1.1l.2-.1c-.7-.6-1.5-1-2.4-1.2-.4-.1-.8-.1-1.3-.1-2.8 0-5.4 2-6 4.9-.1.5-.1 1-.1 1.6 0-.5 0-1 .1-1.4.1-1 .5-1.7 1-2.4z"/> - <path fill="url(#aQ)" fill-rule="nonzero" d="M106.4 207.6c.1 0 .1 0 0 0h.1c1-.3 1.9-.9 2.7-1.6-1.1-.3-2 .2-3.1.7-1 .2-2 .3-2.7 1l-.2.2c1.2.2 2.3 0 3.2-.3z"/> - <path fill="url(#aR)" fill-rule="nonzero" d="M118.3 196.1c.7-1.4.9-3 .6-4.6-.4-2.1-1.5-3.9-3.3-5.1-1.4-1-3-1.4-4.6-1.4-1.5 0-3 .5-4.2 1.3-.2-.2-.4-.3-.6-.5-1.2-.8-2.5-1.2-4-1.2-2.1 0-4 1-5.3 2.6h-.7c-2.7 0-5.2 1.4-6.8 3.6-1.8 2.6-1.9 5.9-.6 8.6-.7.5-1.2 1.1-1.7 1.8-1.3 1.8-1.8 4.1-1.4 6.3.4 2.2 1.6 4.1 3.5 5.4 1.2.9 2.6 1.4 4.1 1.5.4 1.1 1.2 2.1 2.2 2.8 1 .7 2.2 1.1 3.5 1.1 1 .9 2.2 1.6 3.7 2.2l1 5.5h2.3l-1.3-7.2c-1.3-.4-2.7-1-3.8-1.8-.4-.3-.7-.6-1-1h-.1l-.1-.1c-.4-.4-.7-1-.9-1.6v-.1 [...] - <path fill="#EDEDF0" fill-rule="nonzero" d="M107.9 226.5h-.4c-.8 0-1.5-.2-1.5-.6v-.1h-2.3l.1.5c.2 1.2 1.3 2.5 3.8 2.5 1.5 0 3.5-.5 4.6-1.8.2-.2.3-.5.4-.7l-2.7-.3c-.5.2-1.3.4-2 .5z"/> - <path fill="url(#aS)" fill-rule="nonzero" d="M107.9 226.5h-.4c-.8 0-1.5-.2-1.5-.6v-.1h-2.3l.1.5c.2 1.2 1.3 2.5 3.8 2.5 1.5 0 3.5-.5 4.6-1.8.2-.2.3-.5.4-.7l-2.7-.3c-.5.2-1.3.4-2 .5z"/> - <path d="M-30-28h352v303H-30z"/> - </g> -</svg> diff --git a/browser/extensions/onboarding/content/img/figure_default.svg b/browser/extensions/onboarding/content/img/figure_default.svg deleted file mode 100644 index c52e4b8500f7b..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_default.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="272" height="247" viewBox="0 0 272 247" xmlns="http://www.w3.org/2000/svg"><title>default-browser</title><defs><linearGradient x1="-12.708%" y1="-28.803%" x2="102.994%" y2="115.824%" id="a"><stop stop-color="#FFCCD7" offset="40.06%"/><stop stop-color="#EDBEE2" offset="100%"/></linearGradient><linearGradient x1="-78.121%" y1="-55.724%" x2="136.609%" y2="135.651%" id="b"><stop stop-color="#FFE900" offset="28.07%"/><stop stop-color="#FFCC07" offset="32.21%"/><stop stop-color="#F [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_library.svg b/browser/extensions/onboarding/content/img/figure_library.svg deleted file mode 100644 index aad20181b9964..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_library.svg +++ /dev/null @@ -1,689 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="267" height="240"> - <defs> - <linearGradient id="a" x1="-287.251713%" x2="363.382118%" y1="-127.999431%" y2="247.172106%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="b" x1="-8347.28%" x2="11424.26%" y1="-8337.33%" y2="11434.21%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="c" x1="-2354.3122%" x2="2468.01463%" y1="-738.5544%" y2="843.1688%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="d" x1="-11316.73%" x2="8454.81%" y1="-5346.60952%" y2="4068.40952%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="e" x1="-156.148629%" x2="205.305484%" y1="-480.49483%" y2="430.938303%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="f" x1="-11777.11%" x2="7994.43%" y1="-1542.90541%" y2="1128.92432%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="g" x1="-1966.10678%" x2="1385.00169%" y1="-2646.49545%" y2="1847.03636%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="h" x1="-1259.26087%" x2="945.558937%" y1="-1283.95691%" y2="942.373333%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="i" x1="-4828.28387%" x2="3895.46452%" y1="-2550.56897%" y2="2112.12414%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="j" x1="-1420.34388%" x2="1159.68716%" y1="-3565.4194%" y2="2819.67133%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="k" x1="-6578.28%" x2="13193.26%" y1="-6566.33%" y2="13205.21%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="l" x1="-690.589109%" x2="1266.98911%" y1="-1068.60597%" y2="1882.37015%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="m" x1="-3693.78418%" x2="6240.18862%" y1="-1360.99327%" y2="2373.67085%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="n" x1="-51.4002563%" x2="99.3496099%" y1="-59.6430664%" y2="133.087695%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="o" x1="-47.4074974%" x2="121.810771%" y1="-106.87209%" y2="132.306567%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="p" x1="-701.943676%" x2="609.202314%" y1="-537.964802%" y2="487.22249%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="q" x1="-1074.53%" x2="834.91%" y1="-358.218519%" y2="348.981481%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="r" x1="-5230.64688%" x2="3222.21875%" y1="-2856.73793%" y2="1806.91207%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="s" x1="-1536.40601%" x2="955.898444%" y1="-3896.2795%" y2="2345.49035%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="t" x1="-2573.03736%" x2="4141.82528%" y1="-7694%" y2="12077.54%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="u" x1="-105.756%" x2="253.726545%" y1="-959.543678%" y2="1313.04713%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="v" x1="-113.495628%" x2="246.641894%" y1="-1951.93556%" y2="2441.74%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="w" x1="-203.741261%" x2="362.77851%" y1="-8794.04%" y2="10977.5%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="x" x1="-8901.65455%" x2="9072.47273%" y1="-4629.9%" y2="4785.11905%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="y" x1="-135.885507%" x2="273.463147%" y1="-6854.87692%" y2="8354%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="z" x1="-237.240755%" x2="222.496119%" y1="-950.902381%" y2="659.16369%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="A" x1="-323.294457%" x2="276.418625%" y1="-16784.12%" y2="10262.94%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="B" x1="-324.50885%" x2="273.863496%" y1="-16876.15%" y2="10170.29%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="C" x1="-8757.43409%" x2="-13250.9636%" y1="-25788.3267%" y2="-38969.3533%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="D" x1="-4977.81154%" x2="-7512.62308%" y1="-21732.3667%" y2="-32716.5611%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="E" x1="-778.197863%" x2="-1200.66709%" y1="-2873.70382%" y2="-4382.98244%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="F" x1="-3162.7925%" x2="-4810.42083%" y1="-25654.4533%" y2="-38835.4867%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="G" x1="-1053.32338%" x2="1514.40909%" y1="-4984.71765%" y2="6645.6%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="H" x1="-5039.72338%" x2="-7607.45714%" y1="-23040.7706%" y2="-34671.0941%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="I" x1="143.631333%" x2="-4.86%" y1="790.352632%" y2="-381.952632%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="J" x1="-2552.41333%" x2="-3870.516%" y1="-20494.2053%" y2="-30900.2789%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="K" x1="-1250.60304%" x2="-1918.56115%" y1="-38487.33%" y2="-58258.87%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="L" x1="-37598.9%" x2="-57370.44%" y1="-17879.1857%" y2="-27294.2048%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="M" x1="-882.727251%" x2="-1363.78637%" y1="-29434.6846%" y2="-44643.5692%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="N" x1="-268.313828%" x2="273.677355%" y1="-882.118713%" y2="699.481287%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="O" x1="-420.455862%" x2="943.098621%" y1="-4784.28571%" y2="9338.24286%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="P" x1="-587.656122%" x2="1429.84796%" y1="-3859.74375%" y2="8497.475%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="Q" x1="-597.567708%" x2="1461.96771%" y1="-6217.96%" y2="13553.58%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="R" x1="-989.3%" x2="1835.20571%" y1="-6563.19091%" y2="11410.9364%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="S" x1="-1683.03158%" x2="3520.00526%" y1="-4061.93125%" y2="8295.28125%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="T" x1="-289.56383%" x2="551.778298%" y1="-736.619802%" y2="1220.95842%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="U" x1="-8102.24%" x2="11669.3%" y1="-8112.37%" y2="11659.17%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="V" x1="-527.27218%" x2="959.309774%" y1="-7671.89%" y2="12099.65%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="W" x1="-563.298261%" x2="1155.96609%" y1="-4360.425%" y2="7996.7875%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="X" x1="-595.656881%" x2="1218.24587%" y1="-7031.95%" y2="12739.59%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="Y" x1="-4261.16471%" x2="7369.15294%" y1="-5186.16429%" y2="8936.36429%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="Z" x1="-7291.52%" x2="12480.03%" y1="-7323.1%" y2="12448.44%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aa" x1="-46.8866667%" x2="106.777333%" y1="-610.354545%" y2="437.354545%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="ab" x1="-954.992%" x2="1681.21333%" y1="-6801.97273%" y2="11172.1545%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ac" x1="-53.1965517%" x2="108.827586%" y1="-138.8375%" y2="154.825%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="ad" x1="-2268.40345%" x2="4549.36897%" y1="-4153.9%" y2="8203.3125%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ae" x1="-134.196822%" x2="349.214914%" y1="-7485.96%" y2="12285.58%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="af" x1="-203.129153%" x2="467.092542%" y1="-7412.3%" y2="12359.24%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ag" x1="-8254.16%" x2="11517.38%" y1="-4829.67647%" y2="6800.64118%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ah" x1="-261.207831%" x2="281.860241%" y1="-1137.19462%" y2="943.173846%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="ai" x1="-353.298433%" x2="352.892428%" y1="-15403.61%" y2="11643.5%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aj" x1="-355.267885%" x2="350.914099%" y1="-15487.8%" y2="11558.97%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="ak" x1="-2084.69358%" x2="-3141.99572%" y1="-5548.86479%" y2="-8333.58732%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="al" x1="-2136.94011%" x2="-3223.28791%" y1="-39758.41%" y2="-59529.95%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="am" x1="-8671.43111%" x2="-13065.1111%" y1="-39159.26%" y2="-58930.8%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="an" x1="42.05%" x2="39.02%" y1="40.85%" y2="37.83%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ao" x1="-1655.02189%" x2="-2503.58541%" y1="-18008.5045%" y2="-26995.5636%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ap" x1="26.16%" x2="23.82%" y1="17.93%" y2="15.58%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aq" x1="-7321.04%" x2="-10915.8655%" y1="-26976.66%" y2="-40157.6867%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ar" x1="-3806.45143%" x2="-5689.45619%" y1="-33702.4583%" y2="-50178.75%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="as" x1="-719.07449%" x2="1298.42959%" y1="-4375.10588%" y2="7255.21176%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="at" x1="-4193.87653%" x2="-6211.37959%" y1="-24406.3118%" y2="-36036.6294%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="au" x1="-524.679508%" x2="1095.93852%" y1="-4333.45%" y2="8023.7625%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="av" x1="-3315.91393%" x2="-4936.53115%" y1="-25616.6063%" y2="-37973.8188%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aw" x1="-1422.94082%" x2="2612.06735%" y1="-5115.85714%" y2="9006.67143%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ax" x1="-8372.54082%" x2="-12407.5531%" y1="-29439.4643%" y2="-43561.9929%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="ay" x1="-2040.6303%" x2="3950.74545%" y1="-6860.53%" y2="12911.01%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="az" x1="-12359.7364%" x2="-18351.1091%" y1="-40913.58%" y2="-60685.12%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aA" x1="-1005.75152%" x2="1989.93788%" y1="-6296.96364%" y2="11677.1727%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aB" x1="-6165.30303%" x2="-9160.98939%" y1="-37254.2727%" y2="-55228.4%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aC" x1="-2871.84%" x2="5036.776%" y1="-4515.63125%" y2="7841.58125%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aD" x1="-16493.056%" x2="-24401.672%" y1="-25798.7875%" y2="-38156%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aE" x1="-4836.46667%" x2="8344.56%" y1="-7269.91%" y2="12501.63%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aF" x1="-27538.4933%" x2="-40719.52%" y1="-41322.96%" y2="-61094.5%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aG" x1="123.979381%" x2="7.09896907%" y1="645.125%" y2="-299.65%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aH" x1="-4143.41443%" x2="-6181.71959%" y1="-33849.65%" y2="-50325.925%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aI" x1="110.22963%" x2="13.6574074%" y1="263.406667%" y2="-84.2533333%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aJ" x1="-7493.57037%" x2="-11154.9667%" y1="-27110.28%" y2="-40291.3067%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aK" x1="-1314.06588%" x2="-1982.02331%" y1="-40374.36%" y2="-60145.89%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aL" x1="-39504.49%" x2="-59276.05%" y1="-23215.4176%" y2="-34845.7353%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aM" x1="-935.697066%" x2="-1419.10856%" y1="-40260.71%" y2="-60032.24%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aN" x1="-239.365731%" x2="302.59479%" y1="-1057.81832%" y2="1006.59618%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aO" x1="-195.98196%" x2="188.238494%" y1="-262.20413%" y2="218.292299%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aP" x1="-148.239568%" x2="156.504317%" y1="-236.10625%" y2="205.1375%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aQ" x1="-684.479137%" x2="737.933813%" y1="-1012.53646%" y2="1046.99896%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - <linearGradient id="aR" x1="-802.736152%" x2="689.739334%" y1="-1056.80385%" y2="890.777014%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aS" x1="-1124.88665%" x2="549.535228%" y1="-1423.71471%" y2="673.128094%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aT" x1="-465.885211%" x2="339.528169%" y1="-152.931663%" y2="157.298039%"> - <stop stop-color="#FFE900" offset="28.07%"/> - <stop stop-color="#FFCC07" offset="32.21%"/> - <stop stop-color="#FF8119" offset="41.22%"/> - <stop stop-color="#FF0B36" offset="54.35%"/> - <stop stop-color="#FF0039" offset="55.5%"/> - <stop stop-color="#ED00B5" offset="85.24%"/> - </linearGradient> - <linearGradient id="aU" x1="-632.473239%" x2="759.889437%" y1="-217.098158%" y2="319.212821%"> - <stop stop-color="#FFCCD7" offset="40.06%"/> - <stop stop-color="#EDBEE2" offset="100%"/> - </linearGradient> - </defs> - <g fill="none" fill-rule="evenodd"> - <path d="M150.1 145.9v-.2.2zM152.6 147.1c0 .9.3 1.9.9 2.8-.6-.9-.9-1.9-.9-2.8zM149.7 154.2c0-.2-.1-.5-.3-.6.2.2.3.4.3.6 0 0-.1.7.8 1.8-.9-1-.8-1.8-.8-1.8zM229.2 188.9c.4-1.5.7-3 .8-4.4 0-.5.1-1 .1-1.5 0 .5-.1 1-.1 1.5-.1 1.4-.4 2.9-.8 4.4zM103.1 216.7h.8l-.3-.3c-.1.2-.3.3-.5.3zM235.1 153.6v.2c.4.1.8.3 1.1.6.8.7 1 1.8.7 2.7.1-.2.1-.4.1-.6.1-1.3-.7-2.5-2-2.9v-.2l-.1-.9c-1.5-.1-3-.2-4.6-.4l-.3 3.3 5.1-1.8zM245.1 143.8c6.7-3.5 11.1-12.3 10.9-20.8.3 8.5-4.2 17.3-10.9 20.8-3.5 1.8-8.8 2.6- [...] - <path d="M239.9 150.3c-.8.1-1.6.2-2.4.2-.1-.1-.3-.1-.4-.1-2.1-.1-4.3-.2-6.4-.4v.1c2.1.2 4.2.4 6.3.5.1 0 .3 0 .4.1.8 0 1.6-.1 2.4-.2 6.9-.9 11-3.2 15.3-8.7 1.4-1.7 3-4.6 4.1-7.8-1.2 3.2-2.7 5.9-4.1 7.7-4.3 5.4-8.4 7.7-15.2 8.6zM104 200.6c0-.1 0-.1 0 0 0-.1 0-.1 0 0zM145.8 157.9l-.2-.3v-.1l.1.1M140.7 165.2h-.1l-.6.9v.1M252 110.6c-2.8-4.7-6.4-9.1-8.6-11.7 2.2 2.6 5.8 7 8.6 11.7zM206.9 117.5c-.2-.3-.5-.5-.7-.7-.6-.5-1.4-.7-2.1-.7 1 0 2.1.5 2.8 1.4.5.8 1.5 1 2.3.5.1-.1.2-.1.3-.2-.1.1-.2.1 [...] - <path d="M214.8 223.5v-.9c-1.3.2-2.6.4-3.8.6-4.1.6-8.2 1-12.4 1-6.3 0-12.6-.5-18.8-1.7 0 1.3 0 2.3-.1 3.3 4.7-.1 9.6-.2 14.7-.2 7.1 0 13.9.1 20.4.3v-2.4zM159.1 216.9c-.7-1.9-1.3-4.2-2-6.8h-1.3c-3.9-.1-7.9-.4-11.7-1.1v1.9h1c1 0 1.9 1 1.9 2.2v1.4c0 1.2-.8 2.1-1.7 2.2h.2l.4.3c.2-.2.4-.3.7-.3h.8c1.9.1 3.1.4 3.6.9 1.6 1.3 2.6 4.2 2.6 7.5 0 .5-.1 1.2-.2 1.9 3.1-.2 6.3-.4 9.8-.6-.6-1-1.1-2.1-1.6-3.2-.9-1.9-1.8-4.1-2.5-6.3zM235.4 114.9c-.2.1-.3.3-.3.4.1-.1.2-.2.3-.4.1 0 .1 0 0 0zM150.3 134.7 [...] - <path d="M152.3 147.3c-.2-3.1-.3-6.4-.4-9.8.2-1 .4-1.9.5-2.8 0-10.7.9-20.1 2.9-26v-.1c-2 5.9-2.9 15.3-2.9 26.1-.2.9-.3 1.8-.5 2.8v.2c0-.1 0-.2.1-.3 0 3.5.1 6.8.3 9.9 0 .1 0 .1 0 0zM236.4 182.5c-.4 15.2-3.8 25.4-5.2 29-.3 1.4-.7 2.7-1.1 3.8-1.6 4.5-3.5 8.5-5.2 11.1 1.7-2.6 3.7-6.6 5.3-11.2.4-1.1.7-2.4 1.1-3.8 1.4-3.6 4.8-13.7 5.2-29v-2.3s-.1 0-.1-.1v2.5zM149 153.6c.1-.6.3-1.3.6-1.9-.3.6-.6 1.2-.6 1.9h.2c-.1-.1-.2-.1-.2 0zM174.2 215.2v.2h-.1l.1-.2-.1.3c.1 2.1.1 4.1.1 6 0 1.7 0 3.2-.1 4 [...] - <path fill="#FFF" fill-rule="nonzero" d="M7.7 72.3v98.1c0 1 .1 1.2.1 1.2s.2.1 1.2.1h121.1l-.6-1.9c-.9-3.1.3-6.3 2.9-7.9V72.3H7.7zm45.8 65.5c0 1.8-1.5 3.3-3.3 3.3-1.8 0-3.3-1.5-3.3-3.3V98.4c0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3v39.4zm9.8 0c0 1.8-1.5 3.3-3.3 3.3-1.8 0-3.3-1.5-3.3-3.3V105c0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3v32.8zm9.9 0c0 1.8-1.5 3.3-3.3 3.3-1.8 0-3.3-1.5-3.3-3.3v-36.1c0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3v36.1zm20.8 3.1c-.4.1-.8.2-1.1.2-1.3 0-2.6-.8- [...] - <path fill="#FFF" fill-rule="nonzero" d="M127.3 173.2l1.8.1c.1-.2.3-.4.5-.5H9c-2 0-2.5-.4-2.5-2.5V71.2h127v90.2c.2-.1.4-.2.7-.2l3.6-1.1v-4.8c-1.3-2.3-1.2-5 0-7.1V55.4c0-2.3-1.9-4.2-4.2-4.2H6.5c-2.3 0-4.2 1.9-4.2 4.2v118.3c0 2 1.8 3.7 3.9 3.7h90c.3-.6.6-1.2 1.1-1.5.9-.7 2.6-.8 3.8-.8.5 0 .9.2 1.2.5l.5-.4h19.1c1.3-1.2 3-2 4.9-2h.5zm9.1-13.5l-.1-.2.1.2zm-.1-.3v.4l-.3-.9.3.5zm-9.6-101.2c1.6 0 2.9 1.3 2.9 2.9 0 1.6-1.3 2.9-2.9 2.9-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.9 2.9-2.9zm-9.2 0c1.6 0 [...] - <path fill="#D7D7DB" fill-rule="nonzero" d="M138.7 159.7c.3-.5.6-1.1.8-1.7l-1.7-2.8v4.7l.9-.2zM6.2 177.5c-2.2 0-3.9-1.6-3.9-3.7V55.4c0-2.3 1.9-4.2 4.2-4.2h127.1c2.3 0 4.2 1.9 4.2 4.2V148c.5-.9 1.3-1.7 2.2-2.3V55.4c0-3.6-2.9-6.5-6.5-6.5H6.5c-3.6 0-6.5 2.9-6.5 6.5v118.3c0 3.3 2.8 5.9 6.2 5.9h89.2c.2-.8.4-1.5.7-2.2H6.2v.1zM139 167.8l1.1-1.6v-.1l-1.1 1.7c-.2.1-.2.4 0 .5-.1-.1-.1-.3 0-.5z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M6.5 71.2v99.2c0 2 .4 2.5 2.5 2.5h120.5c.2-.2.4-.5.7-.7l-.1-.4H9c-1 0-1.2-.1-1.2-.1s-.1-.2-.1-1.2V72.3h124.7V162c.3-.2.7-.4 1.1-.6V71.2H6.5zM132.8 169.2c-.2-.7-.2-1.4 0-2.1-.3.6-.3 1.4 0 2.1l.7 2.3v-.1l-.7-2.2zM13.3 64c1.6 0 2.9-1.3 2.9-2.9 0-1.6-1.3-2.9-2.9-2.9-1.6 0-2.9 1.3-2.9 2.9 0 1.6 1.4 2.9 2.9 2.9zM22.6 64c1.6 0 2.9-1.3 2.9-2.9 0-1.6-1.3-2.9-2.9-2.9-1.6 0-2.9 1.3-2.9 2.9 0 1.6 1.3 2.9 2.9 2.9zM38.1 64.3H102c1.7 0 3.1-1.4 3.1-3.1V61c [...] - <path fill="#D7D7DB" fill-rule="nonzero" d="M60 101.6c-1.8 0-3.3 1.5-3.3 3.3v32.8c0 1.8 1.5 3.3 3.3 3.3 1.8 0 3.3-1.5 3.3-3.3v-32.8c0-1.8-1.4-3.3-3.3-3.3zM69.9 98.4c-1.8 0-3.3 1.5-3.3 3.3v36.1c0 1.8 1.5 3.3 3.3 3.3 1.8 0 3.3-1.5 3.3-3.3v-36.1c0-1.9-1.5-3.3-3.3-3.3zM50.2 95.1c-1.8 0-3.3 1.5-3.3 3.3v39.4c0 1.8 1.5 3.3 3.3 3.3 1.8 0 3.3-1.5 3.3-3.3V98.4c0-1.8-1.5-3.3-3.3-3.3zM82.8 100.5c-.6-1.7-2.5-2.6-4.2-2-1.7.6-2.6 2.5-2 4.2l13.1 36.1c.5 1.3 1.7 2.2 3.1 2.2.4 0 .8-.1 1.1-.2 1.7-.6 2. [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M40.9 25.6h97.9c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1H116c-2-3.7-7.1-11.7-13.4-12.9-8.4-1.6-10 6.7-10 6.7S87 2.7 73 4.6c-6.5.9-9 4.2-9.8 7.8h.1c.3 0 .6.2.6.5 0 .4.1.7.1 1.1 0 .3-.2.6-.5.6h-.1c-.2 0-.4-.1-.5-.3-.1 1.9.1 3.8.5 5.3h.7c-.1-.3-.2-.6-.4-1-.1-.3.1-.6.4-.7.3-.1.6.1.7.4.3 1 .6 1.7.6 1.7.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-1.3c.4 1.5.9 2.5.9 2.7H40.7c-.6 0-1.1.5-1.1 1.1.1.5.6 1 1.3 1z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M229.2 52.9c.3-1 1.2-3.2 3.8-3.2.3 0 .7 0 1 .1 1.6.3 3.2 1.3 4.8 3 .2.2.6.2.8 0 .2-.2.2-.6 0-.8-1.8-1.9-3.6-3-5.4-3.4-.4-.1-.8-.1-1.2-.1-3.5 0-4.6 3-4.9 4-.1.3.1.6.4.7h.2c.1 0 .2 0 .3-.1.1 0 .2-.1.2-.2zM213.5 48.4c.1 0 .3-.1.4-.2.6-.7 1.5-1.1 2.6-1.4.3-.1.5-.4.4-.7-.1-.3-.4-.5-.7-.4-1.3.4-2.4.9-3.1 1.7-.2.2-.2.6 0 .8.1.2.3.2.4.2zM246.3 56.9h3.3c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-3.3c-.3 0-.6.2-.6.6 0 .3.3.6.6.6zM220.7 46.6c.4.1.7.2 1 .3h.2c. [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M199.6 60.7h54.5c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-12.9c-1.1-2.1-3.9-6.5-7.4-7.2-2.4-.5-3.8.5-4.6 1.6 0 .1-.1.2-.2.3-.6 1-.8 1.9-.8 1.9s-3.1-8-10.9-7c-5.8.8-6 5-5.4 7.8h.7s.1-.1.2-.1c.3-.1.6 0 .7.3l.1.1c.1.2.1.4 0 .5-.1.2-.3.3-.5.3h-.9c.2.9.5 1.4.5 1.5h-13.2.2c-.6 0-1.1.5-1.1 1.1-.1.6.4 1.1 1 1.1z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M173.7 229.1c-.1.2-.1.4-.2.5 0 0-.1 0-.1.1.1 0 .2-.1.3-.1.3-.3.5-1.6.6-3.6h-.1c-.2 1.5-.3 2.6-.5 3.1zM173.1 232c.5 0 1-.1 1.5-.4-.4.2-.9.4-1.5.4-2.1 0-4.3-2.7-6.1-5.8h-.1c1.8 3.2 4 5.8 6.2 5.8z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M231.1 226.7c-2.6 4.8-5.9 8.5-9.7 8.5-1.4 0-2.9-.5-4-1.4-1.7-1.4-2.5-2.9-2.6-7.8-6.4-.2-13.3-.3-20.4-.3-5 0-9.9.1-14.7.2-.2 5.1-1 6.6-2.7 7.9-1.1.9-2.5 1.4-4 1.4-4 0-6.8-3.6-8.7-6.8-.4-.6-.8-1.3-1.1-2-3.4.2-6.7.4-9.8.6-.3 2-1.1 4.5-2.2 5.4-1.1.9-3.1 1.1-4.5 1.1-.6 0-1-.3-1.4-.6l-.6.6H97.4c-1 0-1.9-.7-2.2-1.7l-.3-1.1v-.2c-5.9.7-9.4 1.5-9.4 2.4 0 2.1 17.6 3.7 39.4 3.7 3.5 0 6.8 0 10-.1 12.2 2.1 34.3 3.4 59.5 3.4 38.5 0 69.7-3.2 69.7-7.1 0-2.6 [...] - <path fill="#EDEDF0" fill-rule="nonzero" d="M219.5 231.3c.5.4 1.2.7 1.9.7.6 0 1.3-.2 1.9-.6-.6.4-1.2.6-1.8.6-.7 0-1.4-.3-2-.7-.6-.6-1.2-1.1-1.3-5.3h-.1c.2 4.3.8 4.8 1.4 5.3zM223.3 228.4c.5-.5 1-1.2 1.5-2-.5.8-1 1.4-1.5 2z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M102.1 188.2l-.3-.2c-.1.2-.3.3-.6.3h-.7c-1.6-.1-2.6-.3-3.1-.7l-.6-.6H83c-.6 0-1.1.5-1.1 1.1 0 .6.5 1.1 1.1 1.1h19.4c.1-.4.2-.7.5-.9h-.8v-.1zM156.8 189.1h.1c-.2-.8-.3-1.5-.4-2.2h-.1c.1.7.3 1.4.4 2.2zM27.3 194.1c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6H24c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h3.3zM19.5 193h-1.1c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h1.1c.3 0 .6-.2.6-.6 0-.3-.3-.6-.6-.6zM68.5 194.1c.3 0 .6-.2.6-.6 0-.3-.2-.6-.6-.6h-3.3c-.3 0-.6.2-.6.6 0 .3.2.6.6.6h3.3zM [...] - <path fill="#EDEDF0" fill-rule="nonzero" d="M65.7 230.4c-.3.9-.6 1.7-1.1 2.1-.8.8-2.3.9-3.4.9-.4 0-.8-.3-1-.6l-.5.5H24.5h-.1c2.2 1.6 12.1 2.7 24 2.7 13.5 0 24.4-1.5 24.4-3.4 0-.7-2.7-1.6-7.1-2.2z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M28.3 224.9h32.9c.1 0 .1 0 .2.1-.1-.4-.1-.7-.3-1H27.2v4.6h33.7c.2-.3.3-.7.4-1.1h-33c-.2 0-.4-.2-.4-.4s.2-.4.4-.4h32.9c.1 0 .2 0 .2.1v-1.1c-.1.1-.2.1-.3.1H28.3c-.2 0-.4-.2-.4-.4s.2-.5.4-.5z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M59.2 231.8l.8-.9.3.1c.3.1.5.3.6.5.1.1.2.3.3.3 1.2 0 2.1-.2 2.5-.6.6-.5 1.3-3.3 1.3-5.1 0-2.6-.7-4.6-1.4-5.2-.1-.1-.8-.3-1.9-.4-.1.4-.2.7-.7.8h-.3l-.9-.9H24.3c-.1 0-.3.1-.3.3v1.2c0 .2.1.3.3.3H62l.2.4c1.3 2.4.8 5.9-.4 7.3l-.2.2H24.3c-.1 0-.2 0-.2.1s-.1.2 0 .3l.2 1c0 .1.1.2.2.2h34.7v.1z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M59.7 233.4l.5-.5c.2.3.6.6 1 .6 1.1 0 2.6-.2 3.4-.9.4-.4.8-1.2 1.1-2.1.5-1.5.7-3.3.7-4.3 0-2.8-.8-5.4-1.9-6.5-.4-.4-1.3-.7-2.7-.8h-.6c-.3 0-.4.1-.5.3l-.3-.3h-36c-.9 0-1.7.9-1.7 2v1.2c0 1.1.7 2 1.7 2h.9v4.6h-.9c-.5 0-1 .3-1.3.8-.3.5-.4 1.1-.3 1.7l.2 1c.2.8.8 1.4 1.5 1.5h35.2v-.3zm-35.5-1.8l-.2-1v-.3c0-.1.1-.1.2-.1h37.2l.2-.2c1.3-1.4 1.7-4.9.4-7.3l-.2-.4H24.3c-.1 0-.3-.1-.3-.3v-1.2c0-.2.1-.3.3-.3h35.5l.9.9h.3c.4-.1.6-.5.7-.8 1.2.1 1.8.3 1.9.4 [...] - <path fill="#FFF" fill-rule="nonzero" d="M174.2 215.4v-.2M236.7 157.9c.2-.2.3-.4.3-.6.1-.2.1-.5.1-.7 0 .2-.1.4-.1.6-.1.2-.2.4-.3.7zM161.3 204.2v.1h.1v-.1h-.1zM173.7 229.1c-.1.1-.1.2-.2.3-.4.3-1 .1-1.7-.5-.1 0-.1-.1-.2-.2-.3-.2-.5-.5-.8-.9.9 1.1 1.7 1.8 2.3 1.8h.2s.1 0 .1-.1c.1 0 .2-.1.3-.4z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M233.4 212.2c-.3 1.4-.7 2.7-1.1 3.8-.5 1.4-4.6 12.7-9 15.4-.6.4-1.3.6-1.9.6-.7 0-1.3-.2-1.9-.7-.7-.5-1.3-1-1.3-5.3 0-1.7 0-4.1.2-7.4-6.5 1.5-13.1 2.3-19.7 2.4-7.4.1-14.9-.7-22.1-2.6.2 11.4-.6 12-1.5 12.8-.1.1-.3.2-.5.3-.5.3-1 .4-1.5.4-2.2 0-4.4-2.6-6.2-5.8-2.5-4.2-4.3-9.3-4.6-10.2-1-3-1.9-6.1-2.6-9.2-1.3.1-2.6.1-3.9.1-3.9-.1-7.9-.5-11.7-1.1v3.2c3.9.7 7.8 1 11.7 1.1h1.3c.7 2.7 1.3 5 2 6.8.8 2.2 1.6 4.3 2.6 6.3.5 1.1 1 2.1 1.6 3.2.4.7.7 1.3 1 [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M137.8 148.1c-1.2 2.1-1.3 4.8 0 7.1v.1l1.7 2.8c-.3.6-.6 1.1-.8 1.7l-.8.3-3.6 1.1c-.2.1-.5.2-.7.2-.4.2-.8.4-1.1.6-2.5 1.7-3.8 4.9-2.9 7.9l.6 1.9.1.4c-.2.3-.4.5-.7.7-.2.2-.3.4-.5.5l-1.8-.1h-.6c-1.9 0-3.6.8-4.9 2h10.3l1.8-2.1-.5-1.7-.7-2.2c-.2-.7-.2-1.5 0-2.2.3-1.1 1.2-2.1 2.4-2.5l5.8-1.8c.8-1.4 1.6-3 2.3-4.7l-2.6-4.3c-.5-.9-.7-1.9-.4-2.8.2-.9.8-1.8 1.7-2.3l1.1-.7 4.8-2.9c.9-3.3 1.7-6.9 2.2-10.6 0-12.6 1.1-21.9 3.3-27.6l-.2-.5c-.4-1.2.3-2.4 1. [...] - <path fill="#FFF" fill-rule="nonzero" d="M136.3 159.7v-.3l-.2-.5.2.9M155.9 106.8l-.3-.9.3 1"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M230.3 174.3c0-1-.1-2-.2-2.9-.1-.7-.2-1.4-.2-2.1-.3-.1-.6-.1-1-.2l-.4 4.5c.6.2 1.2.4 1.8.7zM242.5 116.3c-.7-.6-1.6-.9-2.6-1-.8 0-1.6.2-2.3.7.7.6 1.7.9 2.6.9.9 0 1.7-.2 2.3-.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M174.5 205.9c9.6 4.4 20.4 5.7 30.8 3.8 14.7-2.9 21.6-12.6 23.9-20.8.4-1.5.7-3 .8-4.4 0-.5.1-1 .1-1.5.2-2.5.2-5 .2-7.5v-.1c-.6-.3-1.3-.6-1.9-.8l-1.1 11.7c-.1.9-.8 1.5-1.7 1.5l-22.1 3.1c-.9.9-2.9 2.3-6.5 2.5h-.8c-3.3 0-5.3-1.4-6.2-2.3l-24.9-3.6c-.9 0-1.6-.7-1.6-1.5l-1.2-15.4c-2.4.6-4.8 1.4-7.1 2.3.3 2 .6 4.2 1 6.4.1.1.2.2.2.4l.2.9c.6 3.2 2.6 14.1 4.8 23.4v.1h.1v.1h.1c.8 3.7 1.7 7.3 2.9 10.9 2 5.6 4.5 10.3 6.4 12.7.3.3.6.6.8.9.1 0 .1.1.2.2.7.6 1. [...] - <path fill="#FAFAFA" fill-rule="nonzero" d="M152 137.6c.1 3.3.2 6.6.4 9.8l.3-.3v.1c-.1.9.3 1.9.8 2.9.8 1.4 2.1 2.7 3.2 3.7 1.8-1 3.3-1.2 4-1.2l-.3-4.3c0-.5.1-1 .5-1.3.3-.3.8-.5 1.3-.5h.3l1.4-6.1c.1-.4.4-.8.8-.8.4-.1 10.5-2.3 19.1 1.5 7.1 3.1 11.3 7.9 13.1 10.5 1.5-2.6 5.2-7.4 12.7-11 6.3-3.1 15.5-3.4 16.7-2.9.3.1.6.4.7.7.2.6 1.3 4.5 1.9 7.1h.2c.5 0 1 .1 1.3.5.2.2.4.5.4.8 5.4-.1 10.7-.9 14.2-2.7 6.7-3.5 11.1-12.3 10.9-20.7 0-1.2-.2-2.4-.4-3.6-.6-2.9-2-5.9-3.7-8.9-2.8-4.7-6.4-9.1-8.6-1 [...] - <path fill="#FFF" fill-rule="nonzero" d="M161.7 166.1c-3.6.4-6.2-.6-7.8-1.9.3 2.1.7 4.7 1.1 7.6 2.3-.9 4.7-1.7 7.2-2.3l-.1-.8c-.2-.9-.4-1.7-.4-2.6z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M136.4 159.7l-.1-.3"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M156.4 186.9H145c-.1.3-.2.5-.4.7h.8l.3.2c.1-.2.3-.3.6-.3h.7c1.6.1 2.6.3 3.1.7.3.2.5.5.8.8h6c-.2-.7-.4-1.4-.5-2.1zM150.2 182.9c0-.3-.2-.6-.6-.6h-7.5v1.1h7.5c.3 0 .6-.2.6-.5z"/> - <path fill="url(#a)" fill-rule="nonzero" d="M252 110.6c1.7 3 3.1 6 3.7 8.9.2 1.2.4 2.4.4 3.6.2 8.5-4.2 17.3-10.9 20.8-3.5 1.9-8.8 2.6-14.2 2.7v.5l-.3 2.9c2.1.2 4.2.4 6.4.4.1 0 .3 0 .4.1.8 0 1.6-.1 2.4-.2 6.9-.9 11-3.2 15.3-8.7 1.4-1.7 2.9-4.5 4.1-7.7 1.5-4.1 2.4-8.8 1.3-12.8-1.2-4.7-4.7-9.3-7.7-12.5-2.9-3.8-6-7.3-9.5-10.6-.1-.1-.3-.2-.5-.2-.1 0-.3.1-.4.1.2.3.5.6.8.9 2.3 2.7 5.9 7.1 8.7 11.8z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M242.6 98c.2.3.5.6.8.9-.3-.3-.6-.6-.8-.9z"/> - <path fill="url(#b)" fill-rule="nonzero" d="M242.6 98c.2.3.5.6.8.9-.3-.3-.6-.6-.8-.9z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M252 110.6c1.7 3 3.1 6 3.7 8.9.2 1.2.4 2.4.4 3.6 0-1.2-.2-2.4-.4-3.6-.6-2.9-2-6-3.7-8.9z"/> - <path fill="url(#c)" fill-rule="nonzero" d="M252 110.6c1.7 3 3.1 6 3.7 8.9.2 1.2.4 2.4.4 3.6 0-1.2-.2-2.4-.4-3.6-.6-2.9-2-6-3.7-8.9z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M229.9 169.3c.1.7.1 1.4.2 2.1 0-.8-.1-1.5-.2-2.1z"/> - <path fill="url(#d)" fill-rule="nonzero" d="M229.9 169.3c.1.7.1 1.4.2 2.1 0-.8-.1-1.5-.2-2.1z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M220.4 226.2c0 1.8.2 3.1.5 3.3.1.1.3.2.5.2.5 0 1.2-.5 1.9-1.3.5-.5 1-1.2 1.5-2-1.4-.1-2.9-.2-4.4-.2z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M205.3 209.7c-10.4 1.9-21.2.6-30.8-3.8 9.6 4.4 20.3 5.8 30.8 3.8 14.7-2.8 21.6-12.5 23.9-20.8-2.3 8.3-9.2 17.9-23.9 20.8z"/> - <path fill="url(#e)" fill-rule="nonzero" d="M205.3 209.7c-10.4 1.9-21.2.6-30.8-3.8 9.6 4.4 20.3 5.8 30.8 3.8 14.7-2.8 21.6-12.5 23.9-20.8-2.3 8.3-9.2 17.9-23.9 20.8z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M230.1 183c.2-2.5.3-5 .2-7.4 0 2.4 0 4.9-.2 7.4z"/> - <path fill="url(#f)" fill-rule="nonzero" d="M230.1 183c.2-2.5.3-5 .2-7.4 0 2.4 0 4.9-.2 7.4z"/> - <path fill="url(#g)" fill-rule="nonzero" d="M236.4 180.1v-1c-1.9-1.3-3.9-2.4-5.9-3.4 0 .7.1 1.3.1 1.8 2 .8 3.9 1.4 5.8 2.6z"/> - <path fill="url(#h)" fill-rule="nonzero" d="M236.5 172.8c-.1-1.6-.1-3.1-.2-4.6-1.4 1-3.6 1.6-6.4 1.1.1.7.2 1.4.2 2.1.2 1.5.3 3 .4 4.3-.1 0-.1-.1-.2-.1 0 2.5 0 5-.2 7.4 0 .5-.1 1-.1 1.5-.1 1.4-.4 2.9-.8 4.4-2.3 8.3-9.2 18-23.9 20.8-10.4 1.9-21.2.6-30.8-3.8v2.9h.1c.3 0 .6.2.6.5l.3 6.4c2.9.9 10.8 3 23.3 3 7.3-.2 14.5-1.1 21.5-2.9 2.3-.9 4.4-2.2 6.3-3.8.2-.2.6-.2.8 0 .2.2.2.6 0 .8h-.1v.1c-1.9 1.7-4.1 3-6.4 4-.2 2.9-.3 5.7-.3 7.9v1.4c0 1.8.2 3.1.5 3.3.1.1.3.2.5.2.5 0 1.2-.5 1.9-1.3.5-.5 1 [...] - <path fill="url(#i)" fill-rule="nonzero" d="M202.7 108.7v3.5c0 .2 0 .4.1.6.4-.1.8-.1 1.2-.1.5 0 1.1.1 1.6.2.1-.2.2-.5.2-.7v-3.5c0-.9-.7-1.6-1.6-1.6-.8 0-1.5.7-1.5 1.6z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M202.8 112.8c-1.8.3-3.4 1.3-4.5 2.8-.4.7-.3 1.5.2 2.1.1.1.2.2.3.2.8.5 1.8.3 2.3-.5.4-.6 1-1 1.6-1.2h.1c.2-.1.4-.1.5-.1H203.9c.7 0 1.5.2 2.1.7.3.2.5.4.7.7.5.8 1.5 1 2.3.5.1-.1.2-.1.3-.2.6-.5.7-1.4.2-2.1-1-1.4-2.4-2.3-4-2.7-.5-.1-1.1-.2-1.6-.2-.3-.1-.7 0-1.1 0zm5.1 1.6c.1 0 .1.1.2.2s.3.2.4.4c.2.1.3.3.5.5.1.1.2.2.2.3l.3.3c.1.2.1.5.1.7v.1c0 .1-.1.2-.1.2 0 .1 0 .1-.1.2 0 .1-.1.1-.1.1l-.1.1h-.1-.1c-.1 0-.2.1-.2.1h-.1c-.4 0-.7-.1-1-.4-.8-1-2-1.7-3 [...] - <path fill="url(#j)" fill-rule="nonzero" d="M202.9 113.4c-.2 0-.4.1-.6.2-.2.1-.4.1-.5.2h-.1c-.2.1-.3.2-.5.2h-.1c-.2.1-.3.2-.5.3h-.1c-.3.2-.5.4-.8.7l-.1.1c-.2.2-.4.5-.6.7-.3.5-.2 1.2.3 1.5.4.3.9.2 1.2 0l.3-.3c.2-.2.4-.5.6-.7l.1-.1c.2-.1.4-.3.5-.4.1-.1.2-.1.4-.2.1-.1.3-.1.4-.2.2-.1.4-.1.6-.1H204c1.3 0 2.5.6 3.3 1.7.2.3.6.4 1 .4h.1c.1 0 .2 0 .2-.1.1 0 .1 0 .2-.1h.1l.1-.1.1-.1s.1-.1.1-.2.1-.1.1-.2v-.1c0-.2 0-.5-.1-.7l-.3-.3c-.1-.1-.1-.2-.2-.3-.1-.2-.3-.3-.5-.5-.1-.1-.3-.2-.4-.4-1.2-.8-3. [...] - <path fill="url(#k)" fill-rule="nonzero" d="M140 165.4v.7l.6-.9"/> - <path fill="#FFF" fill-rule="nonzero" d="M137.8 166.1l-1.9.6c-.8.2-1.2 1.1-1 1.8l1.1 3.7.2.6.1.2v.1l.1.4c-.5.6-1 1.1-1.4 1.7h2.4c.2-.4.3-.9.3-1.4v-7.7h.1z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M140 168.2l-.1.2c-.2.3-.5.3-.8.2l-.2-.2c-.1-.2-.1-.4 0-.6l1.1-1.6v-.7l-2.2.7v7.7c0 .5-.1 1-.3 1.4h2.3c.1-.5.2-.9.2-1.4v-5.7z"/> - <path fill="url(#l)" fill-rule="nonzero" d="M154.4 170.6c-3.5 1.5-6.8 3.4-9.9 5.6.1.1.1.2.2.4l.2.7c3.1-2 6.3-3.8 9.7-5.2-.1-.6-.1-1.1-.2-1.5z"/> - <path fill="url(#m)" fill-rule="nonzero" d="M156.8 189.1c-.2-.8-.3-1.5-.4-2.2-.8-4.1-1.3-6.9-1.3-6.9 0-.3.1-.5.4-.6.1-.1.2-.1.2-.1.2 0 .3 0 .4.1-.4-2.2-1.1-5.2-1.5-7.4.1-.1.2-.1.4-.2-.4-2.9-.8-5.5-1.1-7.7-1.5-1.3-2.1-2.8-2-3.7v-.1c-1.2-.6-2.1-1.4-2.8-2.1-1.4-1.5-1.7-3-1.7-3.2v-.4c.1-.4.5-.8.9-.8h.3l.2-.2c.1-.6.3-1.3.6-1.9.8-1.9 2.1-3.5 2.7-4.3-.1-3.2-.2-6.5-.3-9.9 0 .1 0 .2-.1.3l-.6 3c-.1.2-.1.5-.2.7-.3 1.1-.5 2.3-.9 3.5-.1.2-.1.4-.2.6v.2l-.1.2-.1.5h-.1l-1.5 4.1c-.1.2-.3.4-.6.3h-.1-. [...] - <path fill="#F9F9FA" fill-rule="nonzero" d="M149.2 153.6s0-.1 0 0c.2 0 .2 0 .3.1.2.1.3.3.3.6 0 0-.1.7.8 1.8.5.6 1.3 1.2 2.5 1.9.2-.9.7-1.7 1.5-2.5s1.5-1.3 2.3-1.7c-1.2-1.1-2.4-2.4-3.2-3.7-.6-.9-.9-1.9-.9-2.8v-.1l-.3.3c-.6.7-1.9 2.4-2.7 4.3-.3.6-.5 1.3-.6 1.9-.2-.2-.1-.2 0-.1z"/> - <path fill="url(#n)" fill-rule="nonzero" d="M136.3 173.1l-.3-.9-1.1-3.7c-.2-.8.2-1.6 1-1.8l1.9-.6 2.2-.7.6-.2h.1l-.7 1-1.1 1.6c-.1.2-.1.4 0 .5 0 .1.1.2.2.2.3.2.6.1.8-.2l.1-.2 2.4-3.5h.1c1.2-2.2 2.4-4.4 3.3-6.7v-.1l-.2-.3-.1-.2-.1-.1-2.9-4.7c-.4-.7-.2-1.6.5-2l4.7-2.9.3-.2.1-.1-1 2.8c-.1.3.1.6.3.7h.1c.2 0 .5-.1.6-.3l1.5-4.1h.1l.1-.5.1-.2v-.2c.1-.2.1-.4.2-.6.3-1.2.6-2.3.9-3.5.1-.2.1-.5.2-.7l.6-3v-.2c.2-.9.3-1.9.5-2.8 0-10.8.9-20.2 2.9-26.1v.1l.7 1.7c.1.2.3.3.5.4h.2c.3-.1.4-.4.3-.7l-1.2- [...] - <path fill="url(#o)" fill-rule="nonzero" d="M237.3 167.2c-.2.3-.6.7-1 1 0 .9.1 1.7.2 2.4.1.5 0 1.3 0 2.2 0 2-.2 4.6 0 6.1v3.5c-.4 15.3-3.8 25.4-5.2 29-.4 1.4-.7 2.6-1.1 3.8-1.6 4.6-3.6 8.6-5.3 11.2-.5.8-1.1 1.5-1.5 2-.7.8-1.4 1.3-1.9 1.3-.2 0-.3-.1-.5-.2-.3-.3-.5-1.5-.5-3.3v-1-.3-.1c0-2.2.2-5.1.3-7.9v-.1c2.4-.9 4.6-2.3 6.5-4h.1c.2-.2.2-.6 0-.8-.2-.2-.6-.2-.8 0-1.9 1.6-4 2.9-6.3 3.8-7.1 1.8-14.3 2.7-21.5 2.9-12.5 0-20.4-2.1-23.3-3l-.3-6.4c0-.3-.3-.5-.6-.5h-.1c-.1 0-.2.1-.3.2-.1.1-.1.2 [...] - <path fill="url(#p)" fill-rule="nonzero" d="M152 160.4c.2-.2 1.1.2 1-.1-.2-.8-.2-1.6 0-2.4-1.2-.7-2-1.3-2.5-1.9-.9-1-.8-1.8-.8-1.8 0-.2-.1-.4-.2-.6-.1 0-.1-.1-.2-.1H149c-.1 0-.2.1-.2.2h-.3c-.5.1-.8.4-.9.8v.4c0 .2.3 1.7 1.7 3.2.6 1 1.5 1.7 2.7 2.3z"/> - <path fill="url(#q)" fill-rule="nonzero" d="M161.9 166s-.1 0-.2.1c.1.9.2 1.8.4 2.6l-.2-2.7z"/> - <path fill="url(#r)" fill-rule="nonzero" d="M241.3 112.7c.1-.2.2-.5.2-.7v-3.5c0-.5-.3-1-.7-1.3-.3-.2-.6-.3-.9-.3-.9 0-1.6.7-1.6 1.6v3.5c0 .2 0 .4.1.6h.3c.3 0 .6-.1.9-.1.6 0 1.2.1 1.7.2z"/> - <path fill="url(#s)" fill-rule="nonzero" d="M235.1 117.3c.3.2.7.2 1 .1.2-.1.4-.2.6-.4.3-.4.7-.7 1.1-1 .7-.4 1.4-.7 2.3-.7 1 0 1.9.4 2.6 1 .3.2.5.4.7.7.4.5 1.1.6 1.6.2.4-.3.6-.9.3-1.4-.2-.4-.5-.7-.8-1-.8-.8-1.8-1.3-2.8-1.5-.8-.2-1.6-.2-2.4-.1-1 .1-1.9.5-2.7 1.1-.3.2-.6.4-.8.7l-.4.4-.4.4c-.6.5-.5 1.2.1 1.5z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M102.5 224.6c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h44.2c.1 0 .2 0 .2.1-.1-.4-.2-.8-.4-1.2H101v5.2h45.2c.2-.3.4-.8.6-1.3H102.4c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h44.2c.1 0 .2 0 .3.1.1-.4.1-.8 0-1.3-.1.1-.2.2-.3.2h-44.1v.2z"/> - <path fill="url(#t)" fill-rule="nonzero" d="M96.8 230.3c.9-.1 1.9-.2 2.9-.3v-.3h-2.6c-.1 0-.2 0-.3.1 0 .2-.1.3 0 .5z"/> - <path fill="url(#u)" fill-rule="nonzero" d="M151.8 225.1c0-2.9-1-5.2-1.9-6-.2-.1-1-.4-2.6-.5-.1.4-.3.9-.9.9l-.4.1-1.2-1H97.1c-.2 0-.3.2-.3.3v1.4c0 .2.2.3.3.3h15.6l34.2-.6.6.6h.1l.2.3 1.1 1.1-.2 1.1c.3 1.4.2 3-.2 4.2l3-.3c.2-.6.3-1.3.3-1.9z"/> - <path fill="#EDEDF0" fill-rule="nonzero" d="M142.6 230.1h-43v-.1c-1 .1-2 .2-2.9.3l.3 1c0 .2.2.3.3.3H144l1.1-1 .5.1c.4.1.6.3.8.6l.4.4c1.6 0 2.8-.3 3.3-.7.5-.4 1.1-2.1 1.5-3.8l-3 .3c-.2.5-.4 1-.6 1.4l-.2 1-5.2.2z"/> - <path fill="url(#v)" fill-rule="nonzero" d="M142.6 230.1h-43v-.1c-1 .1-2 .2-2.9.3l.3 1c0 .2.2.3.3.3H144l1.1-1 .5.1c.4.1.6.3.8.6l.4.4c1.6 0 2.8-.3 3.3-.7.5-.4 1.1-2.1 1.5-3.8l-3 .3c-.2.5-.4 1-.6 1.4l-.2 1-5.2.2z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M112.7 220.7h34.9l-.7-.6"/> - <path fill="url(#w)" fill-rule="nonzero" d="M112.7 220.7h34.9l-.7-.6"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M147.9 221l.1.1c.4.6.7 1.3.8 2l.2-1.1-1.1-1z"/> - <path fill="url(#x)" fill-rule="nonzero" d="M147.9 221l.1.1c.4.6.7 1.3.8 2l.2-1.1-1.1-1z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M99.7 230.1h43l5.1-.3.2-1c-.2.3-.4.5-.6.7l-.3.3H99.7v.3z"/> - <path fill="url(#y)" fill-rule="nonzero" d="M99.7 230.1h43l5.1-.3.2-1c-.2.3-.4.5-.6.7l-.3.3H99.7v.3z"/> - <path fill="url(#z)" fill-rule="nonzero" d="M95.2 231.8c.2 1 1.1 1.7 2.2 1.7h47.3l.6-.6c.3.3.8.6 1.4.6 1.5 0 3.4-.2 4.5-1.1 1.1-.9 1.9-3.4 2.2-5.4.1-.7.2-1.4.2-1.9 0-3.2-1-6.2-2.6-7.5-.6-.4-1.8-.7-3.6-.9h-.8c-.3 0-.6.1-.7.3l-.4-.3H97c-1.2 0-2.2 1-2.2 2.2v1.4c0 1.2 1 2.2 2.2 2.2h1.2v5.2H97c-.7 0-1.3.3-1.8.9-.4.5-.5 1.1-.4 1.7v.2l.4 1.3zm1.6-1.9c.1-.1.2-.1.3-.1h50l.3-.3c.2-.2.4-.4.6-.7.3-.4.5-.9.6-1.4.4-1.3.5-2.8.2-4.2-.2-.7-.4-1.4-.8-2l-.1-.1-.2-.3H97.2c-.2 0-.3-.2-.3-.3v-1.4c0-.2.2-. [...] - <path fill="url(#A)" fill-rule="nonzero" d="M147 223.8c-.1 0-.2-.1-.2-.1h-44.2c-.3 0-.5.2-.5.5s.2.5.5.5h44.2c.1 0 .3-.1.3-.2.1-.1.1-.2.1-.3 0-.2-.1-.3-.2-.4z"/> - <path fill="url(#B)" fill-rule="nonzero" d="M102.5 225.6c-.3 0-.5.2-.5.5s.2.5.5.5H146.9c.2-.1.3-.2.3-.4 0-.1-.1-.3-.2-.4-.1-.1-.2-.1-.3-.1h-44.2v-.1z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M103 210.8h38.8v-5.2h-38.5c-.8 1.1-1 3.5-.3 5.2z"/> - <path fill="url(#C)" fill-rule="nonzero" d="M134.5 203.4c-1.4-.5-2.8-.9-4.2-1.5h-.2l4.2 1.5h.2z"/> - <path fill="url(#D)" fill-rule="nonzero" d="M145.3 203.6c-2.5-.5-5.1-1-7.6-1.8h-.2c2.5.8 5.1 1.4 7.8 1.8z"/> - <path fill="url(#E)" fill-rule="nonzero" d="M103.2 213.9l.4-.1 1 1h40.6c.2 0 .3-.2.3-.3v-1.4c0-.2-.1-.3-.3-.3h-13.3l-29.1.6-.5-.6h-.1l-.2-.3-.9-1.1.1-1.1c-.4-2-.1-4.2.7-5.6l.1-1 4.4-.3h18.5c-.8-.4-1.7-.9-2.6-1.5h-17.1l-.9 1-.4-.1c-.3-.1-.5-.3-.7-.6-.1-.1-.2-.3-.3-.4-1.3 0-2.4.3-2.8.7-.7.6-1.4 3.8-1.4 5.9 0 2.9.8 5.2 1.6 6 .1.1.9.4 2.2.5 0-.5.2-.9.7-1z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M134.3 203.4c-1.4-.5-2.8-.9-4.2-1.5h-7.8c.9.6 1.8 1.1 2.6 1.5h9.4z"/> - <path fill="url(#F)" fill-rule="nonzero" d="M134.3 203.4c-1.4-.5-2.8-.9-4.2-1.5h-7.8c.9.6 1.8 1.1 2.6 1.5h9.4z"/> - <path fill="url(#G)" fill-rule="nonzero" d="M145.3 203.6c.1-.1.1-.2.1-.3l-.2-1.1c0-.2-.1-.3-.3-.3h-7.2c2.5.7 5 1.3 7.6 1.7z"/> - <path fill="url(#H)" fill-rule="nonzero" d="M145.3 203.6c.1-.1.1-.2.1-.3l-.2-1.1c0-.2-.1-.3-.3-.3h-7.2c2.5.7 5 1.3 7.6 1.7z"/> - <path fill="url(#I)" fill-rule="nonzero" d="M142.9 203.4v.3h2.2c.1 0 .1 0 .2-.1-2.6-.4-5.2-1-7.8-1.8h-7.2l4.2 1.5h8.4v.1z"/> - <path fill="url(#J)" fill-rule="nonzero" d="M142.9 203.4v.3h2.2c.1 0 .1 0 .2-.1-2.6-.4-5.2-1-7.8-1.8h-7.2l4.2 1.5h8.4v.1z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M131.8 212.7h-29.6l.5.7"/> - <path fill="url(#K)" fill-rule="nonzero" d="M131.8 212.7h-29.6l.5.7"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M101.9 212.4l-.1-.1c-.3-.6-.6-1.3-.7-2l-.1 1.1.9 1z"/> - <path fill="url(#L)" fill-rule="nonzero" d="M101.9 212.4l-.1-.1c-.3-.6-.6-1.3-.7-2l-.1 1.1.9 1z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M134.5 203.4h-28.1l-4.4.3-.1 1c.1-.3.3-.5.5-.7l.2-.3H143v-.3h-8.5z"/> - <path fill="url(#M)" fill-rule="nonzero" d="M134.5 203.4h-28.1l-4.4.3-.1 1c.1-.3.3-.5.5-.7l.2-.3H143v-.3h-8.5z"/> - <path fill="url(#N)" fill-rule="nonzero" d="M103 216.8h.2c.2 0 .4-.1.5-.3l.3.3H145.4c1-.1 1.7-1.1 1.7-2.2v-1.4c0-1.2-.9-2.2-1.9-2.2h-1v-5.5h1c.6 0 1.1-.3 1.5-.9.2-.3.3-.5.3-.8.1-.3.1-.7 0-1.1l-.2-1.1c-.1-.4-.3-.8-.5-1.1-.4-.1-.7-.3-1-.5l-.5.4h-40.1c-.2 0-.3 0-.5-.1-.4-.1-.7-.3-.9-.6h-.2c-1.2 0-2.9.2-3.9 1.1-1.3 1.3-2 5.5-2 7.4 0 3.2.9 6.2 2.2 7.5.5.4 1.5.7 3.1.9.1.1.3.2.5.2zm-2.8-2.5c-.8-.8-1.6-3.1-1.6-6 0-2.1.8-5.3 1.4-5.9.4-.4 1.5-.6 2.8-.7.1.1.3.3.3.4.2.3.4.5.7.6l.4.1.9-1H144.8c.1 [...] - <path fill="#FAFAFA" fill-rule="nonzero" d="M108.9 193.8c-.2 0-.4-.2-.4-.4s.2-.4.4-.4h37.5c.1 0 .1 0 .2.1l-.3-.9h-38.6v4.1H146c.2-.3.4-.6.5-1h-37.6c-.2 0-.4-.2-.4-.4s.2-.4.4-.4h37.5c.1 0 .2 0 .3.1 0-.3.1-.7 0-1-.1.1-.2.1-.3.1h-37.5v.1z"/> - <path fill="url(#O)" fill-rule="nonzero" d="M104.3 198.9c0 .1.1.2.3.2h13.9c-.4-.4-.9-.8-1.3-1.1h-10.7v-.3h-2.2c-.1 0-.2 0-.2.1-.1.1-.1.1-.1.2l.3.9z"/> - <path fill="url(#P)" fill-rule="nonzero" d="M104.2 189.1c-.1 0-.2.1-.2.2v1.1c0 .1.1.3.3.3h9.3c0-.6.1-1.1.2-1.6h-9.6z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M113.8 189.1h-9.5-.1 9.6z"/> - <path fill="url(#Q)" fill-rule="nonzero" d="M113.8 189.1h-9.5-.1 9.6z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M117.2 198c.4.4.8.8 1.3 1.1h5.7c-.7-.4-1.4-.7-2-1.1h-5z"/> - <path fill="url(#R)" fill-rule="nonzero" d="M117.2 198c.4.4.8.8 1.3 1.1h5.7c-.7-.4-1.4-.7-2-1.1h-5z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M113.8 189.1c-.1.5-.2 1.1-.2 1.6h3.4c.1-.6.2-1.1.4-1.6h-3.6z"/> - <path fill="url(#S)" fill-rule="nonzero" d="M113.8 189.1c-.1.5-.2 1.1-.2 1.6h3.4c.1-.6.2-1.1.4-1.6h-3.6z"/> - <path fill="url(#T)" fill-rule="nonzero" d="M142.9 198h-15.8c.9.4 1.7.8 2.6 1.1h14.4l.9-.8.4.1c.3.1.5.3.7.5.1.1.2.3.3.3 1.3 0 2.4-.2 2.8-.5.7-.5 1.4-2.9 1.4-4.6 0-2.3-.8-4-1.6-4.6-.1-.1-.8-.3-2-.4h-.2c-.1.3-.3.6-.7.7h-.3l-1-.7h-13.3c-.4.5-.7.9-1.1 1.4l16.2-.3.5.5h.1l.2.2.9.8-.1.9c.4 1.6.1 3.2-.7 4.3l-.1.8-4.5.3z"/> - <path fill="url(#T)" fill-rule="nonzero" d="M142.9 198h-15.8c.9.4 1.7.8 2.6 1.1h14.4l.9-.8.4.1c.3.1.5.3.7.5.1.1.2.3.3.3 1.3 0 2.4-.2 2.8-.5.7-.5 1.4-2.9 1.4-4.6 0-2.3-.8-4-1.6-4.6-.1-.1-.8-.3-2-.4h-.2c-.1.3-.3.6-.7.7h-.3l-1-.7h-13.3c-.4.5-.7.9-1.1 1.4l16.2-.3.5.5h.1l.2.2.9.8-.1.9c.4 1.6.1 3.2-.7 4.3l-.1.8-4.5.3z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M146.8 189.1h.2-.2z"/> - <path fill="url(#U)" fill-rule="nonzero" d="M146.8 189.1h.2-.2zM146.8 189.1h.2-.2z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M144.8 189.1h-13.3 13.3z"/> - <path fill="url(#V)" fill-rule="nonzero" d="M144.8 189.1h-13.3 13.3zM144.8 189.1h-13.3 13.3z"/> - <path fill="url(#W)" fill-rule="nonzero" d="M119.8 189.1c-.3.5-.5 1-.6 1.6l10.5-.2c.3-.5.7-.9 1-1.4h-10.9z"/> - <path fill="url(#W)" fill-rule="nonzero" d="M119.8 189.1c-.3.5-.5 1-.6 1.6l10.5-.2c.3-.5.7-.9 1-1.4h-10.9z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M130.8 189.1h-10.9 10.9z"/> - <path fill="url(#X)" fill-rule="nonzero" d="M130.8 189.1h-10.9 10.9zM130.8 189.1h-10.9 10.9z"/> - <path fill="url(#Y)" fill-rule="nonzero" d="M129.7 190.5h.6c.4-.5.7-.9 1.1-1.4h-.7c-.3.5-.6.9-1 1.4z"/> - <path fill="url(#Y)" fill-rule="nonzero" d="M129.7 190.5h.6c.4-.5.7-.9 1.1-1.4h-.7c-.3.5-.6.9-1 1.4z"/> - <path fill="url(#Y)" fill-rule="nonzero" d="M129.7 190.5h.6c.4-.5.7-.9 1.1-1.4h-.7c-.3.5-.6.9-1 1.4z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M131.5 189.1h-.7.7z"/> - <path fill="url(#Z)" fill-rule="nonzero" d="M131.5 189.1h-.7.7zM131.5 189.1h-.7.7zM131.5 189.1h-.7.7z"/> - <path fill="url(#aa)" fill-rule="nonzero" d="M122.2 198c.6.4 1.3.8 2 1.1h5.5c-.9-.4-1.8-.7-2.6-1.1h-4.9z"/> - <path fill="url(#ab)" fill-rule="nonzero" d="M122.2 198c.6.4 1.3.8 2 1.1h5.5c-.9-.4-1.8-.7-2.6-1.1h-4.9z"/> - <path fill="url(#ac)" fill-rule="nonzero" d="M119.8 189.1h-2.5c-.2.5-.3 1-.4 1.6h2.3c.1-.6.3-1.1.6-1.6z"/> - <path fill="url(#ad)" fill-rule="nonzero" d="M119.8 189.1h-2.5c-.2.5-.3 1-.4 1.6h2.3c.1-.6.3-1.1.6-1.6z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M117.2 198H142.9l4.4-.2.1-.8c-.1.2-.3.4-.5.5l-.2.2h-40.2v.3h10.7z"/> - <path fill="url(#ae)" fill-rule="nonzero" d="M117.2 198H142.9l4.4-.2.1-.8c-.1.2-.3.4-.5.5l-.2.2h-40.2v.3h10.7z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M130.3 190.5h-.6l-10.5.2h-1.6 29.5l-.5-.5"/> - <path fill="url(#af)" fill-rule="nonzero" d="M130.3 190.5h-.6l-10.5.2h-1.6 29.5l-.5-.5"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M147.4 191l.1.1c.3.5.6 1 .7 1.6l.1-.9-.9-.8z"/> - <path fill="url(#ag)" fill-rule="nonzero" d="M147.4 191l.1.1c.3.5.6 1 .7 1.6l.1-.9-.9-.8z"/> - <path fill="url(#ah)" fill-rule="nonzero" d="M104.1 200.5c.2 0 .3.1.5.1h40.1l.5-.4c.2.2.6.4 1 .5h.2c1.2 0 2.9-.2 3.8-.8 1.3-1 2-4.2 2-5.7 0-2-.6-3.8-1.4-5-.2-.3-.5-.6-.8-.8-.5-.3-1.5-.6-3.1-.7h-.7c-.3 0-.5.1-.6.3l-.3-.2h-.8c-.3.4-.8.6-1.4.6h-40.2c-.2.3-.4.6-.5.9v1.3c0 1 .8 1.7 1.9 1.7h1v4.1h-1c-.6 0-1.1.2-1.5.7-.4.4-.5 1-.3 1.5l.2.9c.1.3.2.5.4.7.2 0 .5.2 1 .3-.1 0-.1 0 0 0zm0-2.7c.1-.1.1-.1.2-.1H146.7l.2-.2.5-.5c.8-1.1 1.1-2.8.7-4.3-.1-.6-.4-1.1-.7-1.6l-.1-.1-.2-.2h-42.8c-.2 0-.3-.1- [...] - <path fill="url(#ai)" fill-rule="nonzero" d="M146.6 193.1c-.1 0-.1-.1-.2-.1h-37.5c-.2 0-.4.2-.4.4s.2.4.4.4h37.5c.1 0 .2 0 .3-.1.1-.1.1-.2.1-.3 0-.1-.1-.2-.2-.3z"/> - <path fill="url(#aj)" fill-rule="nonzero" d="M108.9 194.6c-.2 0-.4.2-.4.4s.2.4.4.4h37.6c.2 0 .3-.2.3-.3 0-.1-.1-.2-.1-.3-.1-.1-.2-.1-.3-.1h-37.5v-.1z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M101.1 183.6h38.6v-4.1h-38.4c-.6 1-.8 2.8-.2 4.1z"/> - <path fill="url(#ak)" fill-rule="nonzero" d="M98.4 186.4c.1.1.9.3 2.2.4.1-.3.3-.7.8-.7h.3l1 .8h11.9c.2-.5.5-1 .8-1.4l-14.6.2-.5-.5h-.1l-.2-.2-.9-.8.1-.9c-.3-1.2-.2-2.5.2-3.5H97c-.2.7-.3 1.4-.3 2 .1 2.2.9 4 1.7 4.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M120.7 176.7h-17.3l-.9.8h17.9c0-.3.1-.5.3-.8z"/> - <path fill="url(#al)" fill-rule="nonzero" d="M120.7 176.7h-17.3l-.9.8h17.9c0-.3.1-.5.3-.8z"/> - <path fill="#FFF" fill-rule="nonzero" d="M102 177.4c-.3-.1-.5-.3-.7-.5-.1-.1-.2-.3-.3-.3-1.3 0-2.4.2-2.8.5l-.3.3h4.5-.4z"/> - <path fill="url(#am)" fill-rule="nonzero" d="M102 177.4c-.3-.1-.5-.3-.7-.5-.1-.1-.2-.3-.3-.3-1.3 0-2.4.2-2.8.5l-.3.3h4.5-.4z"/> - <path fill="#FFF" fill-rule="nonzero" d="M133 177.5c.2-.3.5-.5.8-.8l-.8.8z"/> - <path fill="url(#an)" fill-rule="nonzero" d="M133 177.5c.2-.3.5-.5.8-.8l-.8.8z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M100 178.9l.1-.8 4.4-.2h15.6c0-.1.1-.2.2-.4H97.9c-.3.5-.7 1.3-.9 2.2h2.5c.2-.3.3-.6.5-.8z"/> - <path fill="url(#ao)" fill-rule="nonzero" d="M100 178.9l.1-.8 4.4-.2h15.6c0-.1.1-.2.2-.4H97.9c-.3.5-.7 1.3-.9 2.2h2.5c.2-.3.3-.6.5-.8z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M133 177.5c-.2.1-.3.2-.4.4l.4-.4z"/> - <path fill="url(#ap)" fill-rule="nonzero" d="M133 177.5c-.2.1-.3.2-.4.4l.4-.4z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M120.2 185.3l-4.7.1c-.3.4-.6.9-.8 1.4h4.1c.3-.6.8-1.1 1.4-1.5z"/> - <path fill="url(#aq)" fill-rule="nonzero" d="M120.2 185.3l-4.7.1c-.3.4-.6.9-.8 1.4h4.1c.3-.6.8-1.1 1.4-1.5z"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M120.1 177.9h4c.7-.7 1.6-1.1 2.6-1.1h.3l3.3.3c.1-.1.2-.2.3-.4h-10c-.1.3-.3.5-.4.8 0 .1 0 .2-.1.4z"/> - <path fill="url(#ar)" fill-rule="nonzero" d="M120.1 177.9h4c.7-.7 1.6-1.1 2.6-1.1h.3l3.3.3c.1-.1.2-.2.3-.4h-10c-.1.3-.3.5-.4.8 0 .1 0 .2-.1.4z"/> - <path fill="url(#as)" fill-rule="nonzero" d="M143.1 186.7c.2 0 .3-.1.3-.3v-1.1c0-.1-.1-.3-.3-.3h-7.9l-1.6 1.6h9.5v.1z"/> - <path fill="url(#at)" fill-rule="nonzero" d="M143.1 186.7c.2 0 .3-.1.3-.3v-1.1c0-.1-.1-.3-.3-.3h-7.9l-1.6 1.6h9.5v.1z"/> - <path fill="url(#au)" fill-rule="nonzero" d="M122 186.7h10.7c.3-.3.6-.6.8-.9l.7-.7h-4.4l-5.8.1c-.7.6-1.4 1.1-2 1.5z"/> - <path fill="url(#av)" fill-rule="nonzero" d="M122 186.7h10.7c.3-.3.6-.6.8-.9l.7-.7h-4.4l-5.8.1c-.7.6-1.4 1.1-2 1.5z"/> - <path fill="url(#aw)" fill-rule="nonzero" d="M140.9 177.9v.3h1c.4-.3.9-.7 1.3-1l-.1-.2c0-.1-.1-.2-.3-.2h-3.6c-.2.4-.5.8-.9 1.1h2.6z"/> - <path fill="url(#ax)" fill-rule="nonzero" d="M140.9 177.9v.3h1c.4-.3.9-.7 1.3-1l-.1-.2c0-.1-.1-.2-.3-.2h-3.6c-.2.4-.5.8-.9 1.1h2.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M133.9 177.5c.9 0 1.7-.3 2.4-.8h-2.5c-.2.3-.5.5-.8.8h.9z"/> - <path fill="url(#ay)" fill-rule="nonzero" d="M133.9 177.5c.9 0 1.7-.3 2.4-.8h-2.5c-.2.3-.5.5-.8.8h.9z"/> - <path fill="url(#az)" fill-rule="nonzero" d="M133.9 177.5c.9 0 1.7-.3 2.4-.8h-2.5c-.2.3-.5.5-.8.8h.9z"/> - <path fill="#D7D7DB" fill-rule="nonzero" d="M133 177.5l-.4.4h5.7c.3-.3.6-.7.9-1.1h-3c-.7.5-1.5.8-2.4.8h-.8v-.1z"/> - <path fill="url(#aA)" fill-rule="nonzero" d="M133 177.5l-.4.4h5.7c.3-.3.6-.7.9-1.1h-3c-.7.5-1.5.8-2.4.8h-.8v-.1z"/> - <path fill="url(#aB)" fill-rule="nonzero" d="M133 177.5l-.4.4h5.7c.3-.3.6-.7.9-1.1h-3c-.7.5-1.5.8-2.4.8h-.8v-.1z"/> - <path fill="url(#aC)" fill-rule="nonzero" d="M132.7 186.7h.9c.5-.6 1.1-1.1 1.6-1.6h-.9l-.7.7-.9.9z"/> - <path fill="url(#aC)" fill-rule="nonzero" d="M132.7 186.7h.9c.5-.6 1.1-1.1 1.6-1.6h-.9l-.7.7-.9.9z"/> - <path fill="url(#aD)" fill-rule="nonzero" d="M132.7 186.7h.9c.5-.6 1.1-1.1 1.6-1.6h-.9l-.7.7-.9.9z"/> - <path fill="url(#aE)" fill-rule="nonzero" d="M143.1 178.1c.1 0 .2 0 .2-.1.1-.1.1-.1.1-.2l-.2-.7c-.4.3-.9.7-1.3 1h1.2z"/> - <path fill="url(#aE)" fill-rule="nonzero" d="M143.1 178.1c.1 0 .2 0 .2-.1.1-.1.1-.1.1-.2l-.2-.7c-.4.3-.9.7-1.3 1h1.2z"/> - <path fill="url(#aF)" fill-rule="nonzero" d="M143.1 178.1c.1 0 .2 0 .2-.1.1-.1.1-.1.1-.2l-.2-.7c-.4.3-.9.7-1.3 1h1.2z"/> - <path fill="url(#aG)" fill-rule="nonzero" d="M127 176.8h-.3c-1 0-1.9.4-2.6 1.1h8.5l.4-.4c.2-.3.5-.5.8-.8h-3c-.1.1-.2.2-.3.4l-3.5-.3z"/> - <path fill="url(#aH)" fill-rule="nonzero" d="M127 176.8h-.3c-1 0-1.9.4-2.6 1.1h8.5l.4-.4c.2-.3.5-.5.8-.8h-3c-.1.1-.2.2-.3.4l-3.5-.3z"/> - <path fill="url(#aI)" fill-rule="nonzero" d="M120.2 185.3c-.6.5-1.1 1-1.5 1.5h3.4c.6-.5 1.3-1 2-1.5h-3.9z"/> - <path fill="url(#aJ)" fill-rule="nonzero" d="M120.2 185.3c-.6.5-1.1 1-1.5 1.5h3.4c.6-.5 1.3-1 2-1.5h-3.9z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M115.4 185.3h4.8l3.9-.1 5.8-.1h-29.6l.6.5"/> - <path fill="url(#aK)" fill-rule="nonzero" d="M115.4 185.3h4.8l3.9-.1 5.8-.1h-29.6l.6.5"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M100.1 184.9l-.1-.1c-.3-.5-.6-1-.7-1.6l-.1.9.9.8z"/> - <path fill="url(#aL)" fill-rule="nonzero" d="M100.1 184.9l-.1-.1c-.3-.5-.6-1-.7-1.6l-.1.9.9.8z"/> - <path fill="#FAFAFA" fill-rule="nonzero" d="M138.4 177.9h-33.8l-4.4.2-.1.8c.1-.2.3-.4.5-.5l.2-.2H141v-.3h-2.6z"/> - <path fill="url(#aM)" fill-rule="nonzero" d="M138.4 177.9h-33.8l-4.4.2-.1.8c.1-.2.3-.4.5-.5l.2-.2H141v-.3h-2.6z"/> - <path fill="url(#aN)" fill-rule="nonzero" d="M97.4 187.5c.5.3 1.5.6 3.1.7h.7c.3 0 .5-.1.6-.3l.3.2h41c.6 0 1.1-.2 1.4-.6.2-.2.4-.5.4-.7 0-.1.1-.3.1-.4v-1.1c0-1-.8-1.7-1.9-1.7h-1v-4h1c.6 0 1.1-.2 1.5-.7.4-.4.5-1 .3-1.5l-.1-.2-.2-.7c0-.1-.1-.3-.2-.4-.3-.6-.9-.9-1.7-.9h-40l-.5.4c-.3-.2-.7-.5-1.2-.5-1.2 0-2.9.2-3.8.8-.4.3-.8.8-1.1 1.5-.3.7-.6 1.5-.7 2.2-.2.8-.3 1.5-.3 2 0 2.1.6 4 1.6 5.2.2.3.4.5.7.7zm-.4-7.8c.2-.9.5-1.8.9-2.2l.3-.3c.4-.3 1.5-.5 2.8-.5.1.1.3.2.3.3.2.2.4.4.7.5l.4.1.9-.8h39. [...] - <path fill="#FFF" fill-rule="nonzero" d="M190.1 151.1c-8.6-5.4-23.3-5.4-23.5-5.4-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6.1 0 7.6-.1 15.1 1.8 5.8 1.5 10 3.7 12.6 6.7h.2c-9.2-10.9-26.7-9.7-26.9-9.6-.3 0-.6-.2-.6-.5s.2-.6.5-.6c.2 0 15.7-1.1 25.6 7.7-2.1-2.3-5.5-5.2-10.1-7.3-6.7-2.9-14.7-1.9-17-1.5l-1.2 5.3 25.1 4.3c0 .3.1.3.2.3zM210.5 142.3c-5 2.4-8.1 5.4-10 7.7 8.8-8.7 22.6-8.9 22.7-8.9.3 0 .6.2.6.6 0 .3-.2.6-.6.6-.2 0-15.1.2-23.3 10 .2-.1.4-.2.6-.4 2.3-2.2 5.4-4 9.5-5.5 6.9-2.5 14.1-3.1 14.2- [...] - <path fill="url(#aO)" fill-rule="nonzero" d="M230.9 147v-.5c-.1-.3-.2-.6-.4-.8-.3-.4-.8-.5-1.3-.5h-.2c-.7-2.6-1.7-6.5-1.9-7.1-.1-.3-.4-.6-.7-.7-1.2-.5-10.5-.2-16.7 2.9-7.5 3.7-11.2 8.5-12.7 11-1.8-2.6-6-7.4-13.1-10.5-8.6-3.7-18.7-1.6-19.1-1.5-.4.1-.8.4-.8.8l-1.4 6.1h-.3c-.5 0-.9.2-1.3.5-.3.3-.5.8-.5 1.3l.3 4.3h.5l.4.1.5 4.9c1.6-.1 6-.5 6.5-.5.8 0 1.3.5 1.4 1.4.2 1.9-1.9 5-7.1 6.2-.3.1-.9.6-1.1.8-.1.1.2.6-.1.8l.2 2.7.1.8.1 1.2 1.2 15.4c.1.9.8 1.5 1.6 1.5l24.9 3.6c.9.9 2.9 2.3 6.2 2.3h [...] - <path fill="url(#aP)" fill-rule="nonzero" d="M223.7 156.1c-.2-.2-.6-.3-.9-.3l-11.5 1.8c-.5.1-.9.5-.9 1.1l-.1 5.5c0 .3.1.6.4.9.2.2.5.3.7.3h.2l10.6-1.6c-.4-.9-.5-1.8-.4-2.4.1-.8.6-1.4 1.4-1.4.1 0 .5 0 .9.1l.1-3.1c-.2-.4-.3-.7-.5-.9z"/> - <path fill="url(#aQ)" fill-rule="nonzero" d="M223.7 156.1c-.2-.2-.6-.3-.9-.3l-11.5 1.8c-.5.1-.9.5-.9 1.1l-.1 5.5c0 .3.1.6.4.9.2.2.5.3.7.3h.2l10.6-1.6c-.4-.9-.5-1.8-.4-2.4.1-.8.6-1.4 1.4-1.4.1 0 .5 0 .9.1l.1-3.1c-.2-.4-.3-.7-.5-.9z"/> - <path fill="#FFF" fill-rule="nonzero" d="M162.8 163.3c4.7-1 6.4-3.7 6.3-5 0-.4-.2-.4-.3-.4-.4 0-4.4.3-6.9.6h-.5l-.6-5.1c-.9 0-3.2.4-5.5 2.6-1.4 1.3-1.7 3.1-.9 4.6.9 2 3.7 3.8 8.4 2.7z"/> - <path fill="url(#aR)" fill-rule="nonzero" d="M161.7 166.1c.1 0 .2 0 .2-.1.3-.2 0-.7.1-.8.2-.2.8-.7 1.1-.8 5.2-1.1 7.3-4.3 7.1-6.2-.1-.8-.6-1.4-1.4-1.4-.5 0-4.9.4-6.5.5l-.5-4.9-.4-.1h-.5c-.8 0-2.3.2-4 1.2-.7.4-1.5 1-2.3 1.7-.8.7-1.3 1.6-1.5 2.5-.2.8-.2 1.6 0 2.4.1.3-.8-.1-1 .1v.1c-.1.9.5 2.4 2 3.7 1.4 1.5 3.9 2.5 7.6 2.1zm-6.5-9.9c2.3-2.3 4.6-2.6 5.5-2.6l.6 5.1h.5c2.6-.2 6.5-.6 6.9-.6.1 0 .2 0 .3.4.1 1.2-1.6 3.9-6.3 5-4.7 1-7.5-.7-8.5-2.5-.7-1.7-.3-3.4 1-4.8z"/> - <path fill="#FFF" fill-rule="nonzero" d="M231.1 156.6l-.6 5.1h-.5c-2.6-.2-6.5-.6-6.9-.6-.1 0-.2 0-.3.4-.1 1.2 1.6 3.9 6.3 5 4.7 1 7.5-.7 8.5-2.5.8-1.5.5-3.3-.9-4.6-2.5-2.4-4.7-2.7-5.6-2.8z"/> - <path fill="url(#aS)" fill-rule="nonzero" d="M236.7 157.9c-3.2-2.7-6.1-2.4-6.2-2.4h-.5l-.5 4.9c-1.2-.1-4-.3-5.5-.5-.5 0-.8-.1-.9-.1-.8 0-1.3.5-1.4 1.4-.1.6.1 1.5.4 2.4.8 1.9 2.7 4 6.2 4.7 1 .2-1.2 0-.4.3.4.1.7.2 1 .3.3.1.6.2 1 .2 2.8.5 5.1-.1 6.4-1.1.4-.3.8-.6 1-1 .4-.5.7-1.6.9-2.2.1-.2.2-.4.2-.5.8-1.6.7-3.3-.2-4.8-.2-.4-.5-.7-.9-1.1-.2-.1-.4-.3-.6-.5zm.8 6c-1 1.8-3.8 3.5-8.5 2.5s-6.4-3.7-6.3-5c0-.4.2-.4.3-.4.4 0 4.4.3 6.9.6h.5l.6-5.1c.9 0 3.2.4 5.5 2.6 1.5 1.5 1.8 3.2 1 4.8z"/> - <path fill="url(#aT)" fill-rule="nonzero" d="M189.7 154.7l.3 33.2s1.1 2.2 6.4 2.8c5.3.6 6.7-3.1 6.7-3.1l.8-33.7s-2.5 2.2-6.7 2.5c-4.2.3-7.5-1.7-7.5-1.7z"/> - <path fill="url(#aU)" fill-rule="nonzero" d="M189.7 154.7l.3 33.2s1.1 2.2 6.4 2.8c5.3.6 6.7-3.1 6.7-3.1l.8-33.7s-2.5 2.2-6.7 2.5c-4.2.3-7.5-1.7-7.5-1.7z"/> - <path d="M-31-22h352v303H-31z"/> - </g> -</svg> diff --git a/browser/extensions/onboarding/content/img/figure_performance.svg b/browser/extensions/onboarding/content/img/figure_performance.svg deleted file mode 100644 index f7c5c219aada8..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_performance.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="297" height="245" viewBox="0 0 297 245" xmlns="http://www.w3.org/2000/svg"><title>performance</title><defs><linearGradient x1="-920.838%" y1="-294.992%" x2="891.374%" y2="366.984%" id="a"><stop stop-color="#FFFBCC" offset="0%"/><stop stop-color="#CEF7C6" offset="100%"/></linearGradient><linearGradient x1="-162.81%" y1="-242.422%" x2="179.364%" y2="239.183%" id="b"><stop stop-color="#FFFBCC" offset="0%"/><stop stop-color="#CEF7C6" offset="100%"/></linearGradient><linearGradien [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_private.svg b/browser/extensions/onboarding/content/img/figure_private.svg deleted file mode 100644 index f90163e4b4d77..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_private.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="289" height="237" viewBox="0 0 289 237" xmlns="http://www.w3.org/2000/svg"><title>private-browsing</title><defs><linearGradient x1="12.376%" y1="17.359%" x2="82.943%" y2="91.352%" id="a"><stop stop-color="#E60024" offset="0%"/><stop stop-color="#ED00B5" offset="51.53%"/><stop stop-color="#8000D7" offset="100%"/></linearGradient><linearGradient x1="-3.914%" y1=".14%" x2="98.417%" y2="106.522%" id="b"><stop stop-color="#E60024" offset="0%"/><stop stop-color="#ED00B5" offset="51 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_screenshots.svg b/browser/extensions/onboarding/content/img/figure_screenshots.svg deleted file mode 100644 index f4930d09f7af5..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_screenshots.svg +++ /dev/null @@ -1,191 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="281" height="233"> - <defs> - <linearGradient id="a" x1="-26.7072552%" x2="121.200691%" y1="-8.21456664%" y2="115.364749%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="b" x1="-171.534367%" x2="377.694136%" y1="-258.916232%" y2="507.082022%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="c" x1="-275.615152%" x2="393.814483%" y1="-214.880097%" y2="329.931438%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="d" x1="-71.2230562%" x2="141.268437%" y1="-46.5567621%" y2="122.213199%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="e" x1="-912.187374%" x2="706.872366%" y1="-223.131903%" y2="247.7375%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="f" x1="-636.509606%" x2="265.115932%" y1="-364.308744%" y2="178.753736%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="g" x1="-96.7324958%" x2="214.858961%" y1="-489.128132%" y2="600.29142%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="h" x1="-370.226425%" x2="176.655533%" y1="-420.236682%" y2="206.08556%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="i" x1="-1573.85207%" x2="2621.18334%" y1="-918.807829%" y2="1582.542%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="j" x1="-1977.10979%" x2="2217.92561%" y1="-1158.35597%" y2="1342.99386%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="k" x1="-635.169191%" x2="1018.69953%" y1="-1184.44408%" y2="1785.60576%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="l" x1="-278.76866%" x2="377.256589%" y1="-697.981967%" y2="835.635246%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="m" x1="-553.131633%" x2="647.619338%" y1="-1374.34047%" y2="1418.49315%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="n" x1="-450.59361%" x2="546.286439%" y1="-895.950857%" y2="958.91224%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="o" x1="-511.211278%" x2="295.07392%" y1="-745.273546%" y2="396.265912%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="p" x1="-871.182847%" x2="303.781403%" y1="-595.928571%" y2="241.5435%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="q" x1="-450.336951%" x2="307.764971%" y1="-505.416691%" y2="315.448433%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="r" x1="-2519.79056%" x2="1944.50093%" y1="-1090.70814%" y2="890.815528%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="s" x1="-134.127826%" x2="165.330874%" y1="-297.102666%" y2="260.202663%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="t" x1="-1132.52358%" x2="304.180944%" y1="-1559.01765%" y2="393.843988%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="u" x1="-1884.94918%" x2="1592.74001%" y1="-342.289711%" y2="381.222953%"> - <stop stop-color="#E6FCFF" offset="0%"/> - <stop stop-color="#B5F2FF" offset="100%"/> - </linearGradient> - <linearGradient id="v" x1="-109.932792%" x2="195.629347%" y1="-425.144051%" y2="431.622036%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="w" x1="-813.648281%" x2="368.736119%" y1="-1076.38789%" y2="459.249729%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="x" x1="-1092.12785%" x2="635.82518%" y1="-4587.46665%" y2="2425.66052%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="y" x1="-415.250984%" x2="1490.35841%" y1="-442.448072%" y2="1582.67684%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="z" x1="-167.167389%" x2="492.546376%" y1="-2085.55413%" y2="4392.09342%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="A" x1="-2989.85248%" x2="1926.86535%" y1="-1363.11821%" y2="921.90878%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - <linearGradient id="B" x1="-2586.45105%" x2="2652.41027%" y1="-792.93501%" y2="883.790987%"> - <stop stop-color="#00C8D7" offset="0%"/> - <stop stop-color="#008EA4" offset="100%"/> - </linearGradient> - </defs> - <g fill="none" fill-rule="evenodd"> - <g fill="#D7D7DB" fill-rule="nonzero"> - <path d="M204.3 76.7h-77c-.6 0-1.1-.5-1.1-1.1 0-.6.5-1.1 1.1-1.1h77c.6 0 1.1.5 1.1 1.1 0 .6-.4 1.1-1.1 1.1zM193.9 71h-13.4c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h13.4c.3 0 .6.2.6.6 0 .4-.2.6-.6.6zM176.4 81.7H163c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h13.4c.3 0 .6.2.6.6 0 .4-.2.6-.6.6zm-22.2 0h-3.3c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h3.3c.3 0 .6.2.6.6 0 .4-.3.6-.6.6zm-7.8 0h-1.1c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h1.1c.3 0 .6.2.6.6 0 .4-.3.6-.6.6zm-11.2 0h-13.4c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h13.4c. [...] - </g> - <g fill-rule="nonzero"> - <path fill="#F9F9FA" d="M152.3 47.8h23.8s-7.4-16.6 8.3-18.8c14.1-1.9 19.6 12.5 19.6 12.5s1.7-8.3 10-6.7c8.3 1.6 14.3 14.8 14.3 14.8H249"/> - <path fill="#D7D7DB" d="M249.5 45.8H245c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h4.5c.3 0 .6.2.6.6-.1.4-.3.6-.6.6zm-14.5 0h-1.1c-.3 0-.6-.2-.6-.6 0-.4.2-.6.6-.6h1.1c.3 0 .6.2.6.6 0 .4-.3.6-.6.6zm-5.6 0h-.6c-.2 0-.4-.1-.5-.3-.1-.2-.6-1.1-1.3-2.3-.2-.3-.1-.6.2-.8.3-.2.6-.1.8.2.6.9 1 1.7 1.2 2.1h.3c.3 0 .6.2.6.6 0 .4-.4.5-.7.5zm-52.9-.7H175c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h.6c-.1-.3-.2-.6-.4-1-.1-.3.1-.6.4-.7.3-.1.6.1.7.4.3 1 .6 1.7.6 1.7.1.2.1.4 0 .5-.1.1-.3.3-.4.3zm-10.4 0h-13.4c-.3 0-.6-.2 [...] - <path fill="#F9F9FA" d="M250.2 50.1h-97.9c-.6 0-1.1-.5-1.1-1.1 0-.6.5-1.1 1.1-1.1h97.9c.6 0 1.1.5 1.1 1.1 0 .6-.5 1.1-1.1 1.1z"/> - </g> - <g fill-rule="nonzero"> - <path fill="#F9F9FA" d="M49.3 29.4h13.2s-4.1-9.2 4.6-10.4c7.8-1.1 10.9 7 10.9 7s.9-4.6 5.6-3.8c4.6.9 8 8.3 8 8.3h11.5"/> - <path fill="#D7D7DB" d="M62.9 27.9H49.7c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h12.8s.1-.1.2-.1c.3-.1.6 0 .7.3l.1.1c.1.2.1.4 0 .5-.2.3-.4.4-.6.4zm36.6-.1h-3.3c-.3 0-.6-.2-.6-.6 0-.3.2-.6.6-.6h3.3c.3 0 .6.2.6.6 0 .3-.3.6-.6.6zm-20.9-3.6h-.2c-.3-.1-.5-.4-.4-.7.3-.9 1.5-4 4.9-4 .4 0 .8 0 1.2.1 1.8.3 3.6 1.5 5.4 3.4.2.2.2.6 0 .8-.2.2-.6.2-.8 0-1.6-1.7-3.2-2.7-4.8-3-.4-.1-.7-.1-1-.1-2.6 0-3.5 2.2-3.8 3.2-.1.1-.3.3-.5.3zm-15.2-4.9c-.1 0-.3-.1-.4-.2-.2-.2-.2-.6 0-.8.8-.8 1.8-1.4 3.1-1.7.3-.1.6.1 [...] - <path fill="#F9F9FA" d="M104 31.6H49.6c-.6 0-1.1-.5-1.1-1.1 0-.6.5-1.1 1.1-1.1H104c.6 0 1.1.5 1.1 1.1 0 .6-.5 1.1-1.1 1.1z"/> - </g> - <g fill-rule="nonzero"> - <path fill="#FFF" d="M19.6 169.1c-2.8 0-5-2.2-5-4.8V46c0-3 2.4-5.4 5.4-5.4h127c3 0 5.4 2.4 5.4 5.4v118.3c0 2.6-2.3 4.8-5 4.8H19.6z"/> - <path fill="#D7D7DB" d="M146.9 41.8c2.3 0 4.2 1.9 4.2 4.2v118.3c0 2-1.8 3.7-3.9 3.7H19.6c-2.2 0-3.9-1.6-3.9-3.7V46c0-2.3 1.9-4.2 4.2-4.2h127zm0-2.2h-127c-3.6 0-6.5 2.9-6.5 6.5v118.3c0 3.3 2.8 5.9 6.2 5.9h127.6c3.4 0 6.2-2.7 6.2-5.9V46c0-3.5-2.9-6.4-6.5-6.4z"/> - </g> - <path fill="#D7D7DB" fill-rule="nonzero" d="M145.8 62.9V161c0 1-.1 1.2-.1 1.2s-.2.1-1.2.1h-122c-1 0-1.2-.1-1.2-.1s-.1-.2-.1-1.2V62.9h124.6zm1.1-1.2H20v99.2c0 2 .4 2.5 2.5 2.5h122c2 0 2.5-.4 2.5-2.5V61.7h-.1z"/> - <g fill="#D7D7DB" fill-rule="nonzero"> - <circle cx="3.8" cy="3.7" r="2.9" transform="translate(23 48)"/> - <circle cx="3" cy="3.7" r="2.9" transform="translate(33 48)"/> - <path d="M115.3 54.9H51.5c-1.7 0-3.1-1.4-3.1-3.1v-.3c0-1.7 1.4-3.1 3.1-3.1h63.8c1.7 0 3.1 1.4 3.1 3.1v.3c0 1.8-1.4 3.1-3.1 3.1z"/> - <g> - <circle cx="3.8" cy="3.7" r="2.9" transform="translate(127 48)"/> - <circle cx="3.1" cy="3.7" r="2.9" transform="translate(137 48)"/> - </g> - </g> - <g transform="translate(149 84)"> - <ellipse cx="42.7" cy="142" fill="#EDEDF0" fill-rule="nonzero" rx="42.5" ry="6.5"/> - <path fill="#F9F9FA" fill-rule="nonzero" d="M121.2 99.6c-1.3-3.1-4.3-5.2-7.7-5.2-.7 0-1.4.1-2.1.3-.8 0-3.1-.3-7.2-2-1.7-.7-4.8-3.9-8.4-10.5 5.2-19.9 5.5-36.8.7-50.3-.4-1-.9-2.1-1.5-3.2l-.3-1.4 2-1.7c1.6-1.4 2.3-3.5 1.7-5.6-.3-1.2-1-2.2-2-2.9 0-.3 0-.6-.1-.9-.4-2.3-2.2-4.1-4.5-4.4-.4-.1-10.6-1.7-17.1-1.7h-.4l-1.7-2.8C70 3.1 65.5.6 60.5.6c-2.6 0-5.2.7-7.5 2.1-2.6 1.6-4.5 3.9-5.7 6.7-6 .7-12.1 2.3-18.2 4.7l-3.4-1.4c-1.7-.7-3.5-1.1-5.4-1.1-5.8 0-10.9 3.5-13.1 8.8-2.7 6.6-.1 14 5.8 17.5 [...] - <path d="M115.2 101.4c-.4-.9-1.5-1.4-2.4-1-.2.1-.6.1-1.2.1-1.4 0-4.6-.3-9.8-2.4-5.5-2.2-10.3-10.6-12.7-15.5-.1-.2-.1-.5-.1-.8 5.4-19.5 5.9-35.8 1.4-48.4-.3-.8-.7-1.8-1.3-2.7-.1-.1-.1-.2-.1-.3L87.7 25c-.1-.4 0-.8.4-1.1l2.6-2.2-5.8-.9c-.5-.1-.9-.4-.9-.9s.2-.9.6-1.2l3-1.6c-3.5-.5-8.9-1.1-12.7-1.1-1.1 0-2 .1-2.6.2l-.4.1c-.4.1-.9-.1-1.1-.5l-3.4-5.5C66 8 63.5 6.7 60.9 6.7c-1.4 0-2.8.4-4.1 1.2-2.2 1.4-3.5 3.7-3.6 6.3 0 .6-.5 1-1.1 1.1-7.2.4-14.7 2.2-22.2 5.4-.3.1-.6.1-.9 0l-5.4-2.2c-.9-.4 [...] - <path fill="url(#a)" fill-rule="nonzero" d="M114.6 98c-.8-2.1-2.9-3.4-5.1-3.4-.6 0-1.1.1-1.7.3-.7 0-3.4 0-8.7-2.2-3-1.2-6.8-6-10.3-12.8 5.4-19.8 5.7-36.5 1-49.7-.3-1-.8-2-1.4-3l-.9-3.4 3.3-2.8c.8-.7 1.1-1.7.8-2.7-.3-1-1.1-1.7-2.1-1.9l-.7-.1c.5-.6.8-1.4.6-2.2-.2-1.1-1-2-2.1-2.1-.1 0-10.3-1.6-16.7-1.6-.7 0-1.3 0-1.9.1l-2.6-4.2C64 2.9 60.4.9 56.4.9c-2.1 0-4.2.6-6 1.7-2.5 1.6-4.3 4.1-5 6.9-6.6.6-13.5 2.3-20.3 5.1l-4.4-1.8c-1.4-.6-2.8-.8-4.3-.8-4.7 0-8.8 2.8-10.6 7.1-2.4 5.8.4 12.5 6.2 [...] - <path fill="url(#b)" fill-rule="nonzero" d="M36.6 40.6c-1.1 0-2.2-.2-3.3-.7l-16.2-6.6c-4.5-1.8-6.7-7-4.8-11.5 1.8-4.5 7-6.7 11.5-4.8L40 23.6c4.5 1.8 6.7 7 4.8 11.5-1.4 3.4-4.7 5.5-8.2 5.5z"/> - <path fill="url(#c)" fill-rule="nonzero" d="M70.8 39.3c-2.9 0-5.8-1.5-7.5-4.2L53.1 18.6c-2.6-4.1-1.3-9.6 2.8-12.1C60 3.9 65.5 5.2 68 9.3l10.2 16.5c2.6 4.1 1.3 9.6-2.8 12.1-1.4 1-3 1.4-4.6 1.4z"/> - <path fill="url(#d)" fill-rule="nonzero" d="M28.6 19.4c-2.2.9-12.8 10.5-11.1 37.1 1.7 26.2-21.6 21.8-3.8 53.4 3.9 6.9 50.2 17.7 58.6 12.7 2.5-1.5 31.6-54.6 19.1-89.8-4.1-11.5-28.5-28-62.8-13.4z"/> - <path fill="url(#e)" fill-rule="nonzero" d="M14.3 87.5s-2.6 17.8-1.7 26.6c1 8.8 3.3 13.7 5.1 12.8 1.7-.8 6.2-26.8 6.2-26.8l-9.6-12.6z"/> - <path fill="url(#f)" fill-rule="nonzero" d="M80.7 103s-5.5 17.1-10.3 24.6c-4.8 7.5-9.1 10.8-10.2 9.3-1.2-1.5 6.2-26.8 6.2-26.8l14.3-7.1z"/> - <path fill="url(#g)" fill-rule="nonzero" d="M33.5 19c7.8-4 28.9-2.7 38.4-4.1C77 14.1 91 16.3 91 16.3l-6 3.2 8.2 1.2-4.5 3.8 1.8 7.3-1.3-.7-46.3-12.8-9.4.7z"/> - <path fill="url(#h)" fill-rule="nonzero" d="M111.4 105.1c-2.3 0-6-.6-11.5-2.8-10-4-16.7-20.9-17.4-22.9-.6-1.5.2-3.2 1.7-3.8 1.5-.6 3.2.2 3.8 1.7 1.7 4.5 7.7 16.9 14.1 19.5 7.1 2.9 10.2 2.3 10.2 2.3 1.5-.6 3.2.1 3.8 1.6.6 1.5-.1 3.2-1.6 3.8-.4.3-1.4.6-3.1.6z"/> - <path fill="#FFF" fill-rule="nonzero" d="M35.4 29.8c-8.3 5.5-3.2 72.6 2.7 79.8 9.5 11.8 31.7 9.3 34.6 3 1.1-2.3 26-48.2 14.3-79.8-3-8-22.5-22.3-51.6-3z"/> - <path fill="url(#i)" fill-rule="nonzero" d="M50.3 43.8c.9.2 1.4 1.1 1.2 1.9l-.8 3.5c-.2.9-1.1 1.4-1.9 1.2-.9-.2-1.4-1.1-1.2-1.9l.8-3.5c.2-.9 1.1-1.4 1.9-1.2z"/> - <path fill="url(#j)" fill-rule="nonzero" d="M81.4 44.8c.9.2 1.4 1.1 1.2 1.9l-.8 3.5c-.2.9-1.1 1.4-1.9 1.2-.9-.2-1.4-1.1-1.2-1.9l.8-3.5c.2-.9 1-1.4 1.9-1.2z"/> - <path fill="url(#k)" fill-rule="nonzero" d="M48.9 57.6c-.5 0-1-.1-1.5-.2-3.5-.8-4.7-3.9-4.7-4.1-.3-.8.1-1.6.9-1.9.8-.3 1.6.1 1.9.9 0 .1.7 1.8 2.6 2.2 1.9.5 3.3-.8 3.3-.8.6-.6 1.5-.5 2.1 0 .6.6.5 1.5 0 2.1-.2.1-2 1.8-4.6 1.8z"/> - <path fill="url(#l)" fill-rule="nonzero" d="M56.6 69.2c-.8 0-1.4-.6-1.5-1.3-.1-.8.5-1.5 1.3-1.6 8.9-.7 17.1-2.5 18-3.8 1-1.7 1.2-4 1.2-4.1 0-.8.7-1.4 1.4-1.4.8 0 1.4.5 1.5 1.3.1 1.3.6 3.4 1.2 4.1 1.1 1.3 2.3 1.2 2.3 1.2.8 0 1.5.6 1.6 1.4.1.8-.6 1.5-1.4 1.6-1 .1-3.2-.3-4.8-2.3-.1-.2-.3-.4-.4-.6-.1.1-.1.2-.2.3-2 3.3-14.8 4.7-20.3 5.2h.1z"/> - <g fill-rule="nonzero"> - <path fill="url(#m)" d="M2.4 4.3C1.3 5 7.7 8.2 8.6 8.2c1.3 0 7.8-2.8 7.6-5C16 2.1 6.8 1.3 2.4 4.3z" transform="translate(70 52)"/> - <path fill="url(#n)" d="M8.6 9.7C7.5 9.7 1.5 7 .9 5c-.2-.8.1-1.5.7-2C5.8.2 13.9.3 16.3 1.4c1 .4 1.2 1.1 1.3 1.6.1.9-.2 1.7-1 2.6-1.8 2.1-6.4 4.1-8 4.1zm-3.9-5c1.3.8 3.5 1.9 4.1 2 .9-.1 4.3-1.7 5.5-2.8-2-.4-6.5-.5-9.6.8z" transform="translate(70 52)"/> - </g> - <g fill-rule="nonzero"> - <path fill="#C8C8CC" d="M115 92.8l-7.2.1-.5-40.7c0-3.3 2.5-6.1 5.7-6.3.3 0 .5.2.5.4l1.5 46.5z"/> - <path fill="#E1E1E6" d="M130.1 53.3c.2-.2.5-.1.7.1 1.9 2.7 1.4 6.4-1.1 8.5l-31.3 26-4.6-5.5 36.3-29.1z"/> - <path fill="url(#o)" d="M.7 10c-.4 2.6.2 5.2 1.9 7.1.8 1 1.8 1.7 2.9 2.3 3.5 1.6 7.8 1 11-1.7.2-.2.5-.4.7-.6l10.1-8.4c.4-.4.7-.9.8-1.4.1-.6-.1-1.1-.5-1.5l-2.9-3.4c-.2-.2-.4-.4-.7-.6-.2-.1-.5-.2-.7-.2-.6-.1-1.1.1-1.5.5l-2.9 2.4c-.1-.2-.2-.3-.4-.5-.8-1-1.8-1.7-2.9-2.3-3.5-1.6-7.8-1-11 1.7C2.5 5.1 1.2 7.5.7 10zm6.6-3.4c1.9-1.6 4.5-2.1 6.5-1.1.6.3 1.1.7 1.5 1.1 1.4 1.6 1.3 4.1.1 6.1-.5.7-1.1 1.4-2 2.1-1.9 1.3-4.2 1.5-5.9.7-.6-.3-1.1-.7-1.5-1.1-.8-1-1.2-2.4-.9-3.8 0-1.5.9-2.9 2.2-4z" [...] - <path fill="url(#p)" d="M0 2.5l.2 13.2v.9c.1 4.1 2.3 7.8 5.7 9.4 1.2.6 2.5.9 3.8.8 5.1-.1 9.3-4.7 9.2-10.4-.1-4.1-2.3-7.8-5.7-9.4-1.2-.6-2.5-.9-3.8-.8h-.6V2.4c0-.8-.5-1.5-1.2-1.9C7.3.4 7 .3 6.7.3L2.2.4C1.6.4 1.1.6.7 1 .2 1.4 0 2 0 2.5zm11.3 8.3c1.9.9 3.2 3.1 3.3 5.6 0 3.4-2.2 6.1-5 6.2-.7 0-1.3-.1-1.9-.4-1.8-.8-3-2.7-3.2-4.9v-.1c0-1.2.1-2.1.3-2.9.7-2.2 2.5-3.9 4.7-3.9.5 0 1.2.1 1.8.4z" transform="translate(107 83)"/> - <path fill="#C8C8CC" d="M111.3 70.6c1.3.1 2.2 1.3 2.1 2.5-.1 1.3-1.3 2.2-2.5 2.1-1.3-.1-2.2-1.3-2.1-2.5.1-1.2 1.2-2.2 2.5-2.1z"/> - </g> - <path fill="url(#q)" fill-rule="nonzero" d="M1.4 2.1L.3 5.7c-1 3.1.7 6.4 3.8 7.4 3.1 1 6.4-.7 7.4-3.8L14.4.1l-13 2z" transform="translate(57 67)"/> - <path fill="url(#r)" fill-rule="nonzero" d="M63.3 74.7h-.2c-.4-.1-.6-.5-.5-.9l2.2-6.8c.1-.4.5-.6.9-.5.4.1.6.5.5.9L64 74.2c-.1.3-.4.5-.7.5z"/> - <path fill="url(#s)" fill-rule="nonzero" d="M58.7 98.1c-17.5 0-33-27.8-33.6-29-.8-1.4-.3-3.2 1.2-4 1.4-.8 3.2-.3 4 1.2 4.2 7.6 17.5 27 29.4 25.9 15.2-1.4 22.4-6.9 22.4-7 1.3-1 3.1-.8 4.1.5 1 1.3.8 3.1-.4 4.1-.3.3-8.5 6.7-25.6 8.2-.5.1-1 .1-1.5.1z"/> - <path fill="url(#t)" fill-rule="nonzero" d="M112.5 97.8s-8 3.2-8.1 5.9c-.1 2.7 8.2 6 11.8.7 3.6-5.2-2.3-7.2-3.7-6.6z"/> - <path fill="url(#u)" fill-rule="nonzero" d="M30.5 65.3s.7 5.9 4.4 9.2c3.7 3.3-4.8 8.1-4.4 15.4.4 7.4 0-24.6 0-24.6z"/> - <path fill="url(#v)" fill-rule="nonzero" d="M58.8 98.9h-1.1C44 98.5 32 81 31.5 80.2c-.2-.3-.1-.8.2-1 .3-.2.8-.1 1 .2.1.2 12.1 17.7 25 18 12.8.3 25.3-6.2 27.1-7.7.5-.4.9-2.6.2-3.7-.7-1-2.4-.3-3.6.5-.3.2-.8.1-1-.2-.2-.3-.1-.8.2-1 3.4-2.1 4.9-.9 5.6-.1 1.2 1.6.8 4.7-.4 5.7-1.2 1-13.4 8-27 8z"/> - <path fill="url(#w)" fill-rule="nonzero" d="M110.8 108.3c-1.3 0-2.8-.3-4.4-1.3-1.9-1-2.8-2.2-2.7-3.6.2-2.7 4.7-4.5 5.2-4.7.4-.1.8 0 1 .4.1.4 0 .8-.4 1-1.6.6-4.2 2.1-4.3 3.5-.1.9 1 1.7 1.9 2.2 2.2 1.2 4.3 1.4 6.1.6 2.1-1 3.1-2.8 3.2-3.2.1-.6.5-2.4-.5-3.5-.7-.8-2.1-1.1-4.1-.9-.4 0-.8-.3-.8-.7 0-.4.3-.8.7-.8 2.5-.2 4.3.2 5.3 1.4 1.5 1.6 1 4 .8 4.7-.2.9-1.6 3.2-4 4.3-.8.3-1.8.6-3 .6z"/> - <path fill="url(#x)" fill-rule="nonzero" d="M61.1 125.5c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7 3.2 0 8.1-1 8.2-1 .4-.1.8.2.9.6.1.4-.2.8-.6.9-.2-.1-5.1.9-8.5.9z"/> - <path fill="url(#y)" fill-rule="nonzero" d="M23 25.4h-.2c-.4-.1-.6-.5-.5-.9.2-.7 2.4-5 7.8-7.4.4-.2.8 0 1 .4.2.4 0 .8-.4 1-4.7 2-6.7 5.8-6.9 6.4-.2.3-.5.5-.8.5z"/> - <path fill="url(#z)" fill-rule="nonzero" d="M68.5 14.8c-8.9 0-18.2-1.2-18.3-1.2-.4-.1-.7-.4-.6-.8.1-.4.4-.7.8-.6.1 0 14.1 1.8 24.1 1 .4 0 .8.3.8.7 0 .4-.3.8-.7.8-2 0-4 .1-6.1.1z"/> - <path fill="url(#A)" fill-rule="nonzero" d="M88.8 89h-.2c-.4-.1-.6-.5-.5-.9l2-6c.1-.4.5-.6.9-.5.4.1.6.5.5.9l-2 6c-.1.3-.4.5-.7.5z"/> - <path fill="url(#B)" fill-rule="nonzero" d="M21 119.1h-.1c-.4-.1-.7-.5-.6-.9l1.7-8.6c.1-.4.5-.7.9-.6.4.1.7.5.6.9l-1.7 8.6c-.2.4-.5.6-.8.6z"/> - </g> - <path fill="#D7D7DB" fill-rule="nonzero" d="M70.8 82.4c-3.7 0-6.6 3-6.6 6.6h6.6v-6.6zm20 0h-6.6V89h6.6v-6.6zm13.3 0V89h6.6c0-3.6-3-6.6-6.6-6.6zm-23.3 0h-6.6V89h6.6v-6.6zm19.9 0h-6.6V89h6.6v-6.6zm3.4 16.6h6.6v-6.6h-6.6V99zm0 20c3.7 0 6.6-3 6.6-6.6h-6.6v6.6zm0-10h6.6v-6.6h-6.6v6.6zm-1.5-7.2c-2.1-3-6.2-3.7-9.3-1.6l-12.7 9.4-6.5-4.6c0-.3.1-.6.1-1 0-2.7-1.3-5.1-3.3-6.6v-5h-6.6v3.5c-3.8.8-6.6 4.1-6.6 8.1 0 4.6 3.7 8.3 8.3 8.3 1.8 0 3.5-.6 4.8-1.6l4.1 2.9-4.6 3.3c-1.3-.8-2.7-1.2-4.3-1.2-4.6 [...] - <g fill="#D7D7DB" fill-rule="nonzero"> - <path d="M17.5 26.8l-.1-.1.1.1zM266.5 1.5v4.4c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V3h-2.9c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5h4.4c.8 0 1.5.7 1.5 1.5zM266.5 14.4v8.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5zm0 17V40c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.6 1.5 1.4zm0 17.1V57c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5zm0 17V74c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-8.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1 [...] - </g> - <path d="M-18-32h352v303H-18z"/> - </g> -</svg> diff --git a/browser/extensions/onboarding/content/img/figure_singlesearch.svg b/browser/extensions/onboarding/content/img/figure_singlesearch.svg deleted file mode 100644 index 9be029397ccfe..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_singlesearch.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="303" height="253" viewBox="0 0 303 253" xmlns="http://www.w3.org/2000/svg"><title>search</title><defs><linearGradient x1="-18.632%" y1="-397.383%" x2="117.795%" y2="492.152%" id="a"><stop stop-color="#00C8D7" offset="0%"/><stop stop-color="#0A84FF" offset="100%"/></linearGradient><linearGradient x1="-312.046%" y1="-3945.649%" x2="293.266%" y2="2768.992%" id="b"><stop stop-color="#00C8D7" offset="0%"/><stop stop-color="#0A84FF" offset="100%"/></linearGradient><linearGradient x [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_sync.svg b/browser/extensions/onboarding/content/img/figure_sync.svg deleted file mode 100644 index 74562d37236da..0000000000000 --- a/browser/extensions/onboarding/content/img/figure_sync.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="279" height="212" viewBox="0 0 279 212" xmlns="http://www.w3.org/2000/svg"><title>sync</title><defs><linearGradient x1="-424.525%" y1="-219.797%" x2="201.215%" y2="136.157%" id="a"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradient x1="-1416.558%" y1="-1417.275%" x2="631.855%" y2="631.14%" id="b"><stop stop-color="#CCFBFF" offset="0%"/><stop stop-color="#C9E4FF" offset="100%"/></linearGradient><linearGradient x1= [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/figure_tor-circuit-display.png b/browser/extensions/onboarding/content/img/figure_tor-circuit-display.png new file mode 100644 index 0000000000000..ea6ecb7f82a32 Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-circuit-display.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-expect-differences.png b/browser/extensions/onboarding/content/img/figure_tor-expect-differences.png new file mode 100644 index 0000000000000..36970bd711a81 Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-expect-differences.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-network.png b/browser/extensions/onboarding/content/img/figure_tor-network.png new file mode 100644 index 0000000000000..87829397ab2a4 Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-network.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-onion-services.png b/browser/extensions/onboarding/content/img/figure_tor-onion-services.png new file mode 100644 index 0000000000000..018345e4b3a0c Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-onion-services.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-privacy.png b/browser/extensions/onboarding/content/img/figure_tor-privacy.png new file mode 100644 index 0000000000000..38201ca5c8788 Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-privacy.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-security-level.png b/browser/extensions/onboarding/content/img/figure_tor-security-level.png new file mode 100644 index 0000000000000..9a5c221c8d8ec Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-security-level.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-security.png b/browser/extensions/onboarding/content/img/figure_tor-security.png new file mode 100644 index 0000000000000..6eb7e5a9995c4 Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-security.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-toolbar-layout.png b/browser/extensions/onboarding/content/img/figure_tor-toolbar-layout.png new file mode 100644 index 0000000000000..6d8651e58c17d Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-toolbar-layout.png differ diff --git a/browser/extensions/onboarding/content/img/figure_tor-welcome.png b/browser/extensions/onboarding/content/img/figure_tor-welcome.png new file mode 100644 index 0000000000000..1bf27c5b93117 Binary files /dev/null and b/browser/extensions/onboarding/content/img/figure_tor-welcome.png differ diff --git a/browser/extensions/onboarding/content/img/icons_addons.svg b/browser/extensions/onboarding/content/img/icons_addons.svg deleted file mode 100644 index 6b27dea39252a..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_addons.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16"><title>Icons / Extension</title><g fill="none"><path d="M0 0h16v16H0z"/><path d="M22.5 16c-1 0-1 1-1.7 1-.5 0-.8-.3-.8-.7V13c0-.6-.4-1-1-1h-3.2c-.5 0-.8-.3-.8-.7 0-.8 1-.8 1-1.8 0-.9-.9-1.5-2-1.5s-2 .6-2 1.5c0 1 1 1 1 1.8 0 .4-.3.7-.7.7H9c-.6 0-1 .4-1 1v2.3c0 .4.3.7.8.7.7 0 .7-1 1.7-1 .9 0 1.5.9 1.5 2s-.6 2-1.5 2c-1 0-1-1-1.7-1-.5 0-.8.3-.8.8V23c0 .6.4 1 1 1h3.3c.4 0 .7-.3.7-.7 0-.8-1-.8-1-1.8 0-.9.9-1.5 2 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_customize.svg b/browser/extensions/onboarding/content/img/icons_customize.svg deleted file mode 100644 index ae0a9409fa5c7..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_customize.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>Glyph / Customize</title><g id="Symbols" fill="none" fill-rule="evenodd"><g id="Glyph-/-Customize" fill-rule="nonzero" fill="#3E3D40"><path d="M4 10c-.886.002-1.665.59-1.91 1.44 0 .01-.015.015-.018.025-.362 1.135-.705 2.11-1.76 2.573l-.022.012-.024.012c-.162.086-.265.254-.266.438 0 .276.224.5.5.5 1.74.12 3.46-.414 4.825-1.5.006-.006.007-.013.013-.02.62-.55.832-1.428.534-2.202C5.575 10.504 4.83 9.995 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_default.svg b/browser/extensions/onboarding/content/img/icons_default.svg deleted file mode 100644 index 235f7d65b6856..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_default.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><title>default-browser-16</title><path fill="context-fill" d="M8,6s0-4,3.5-4S15,5,15,6c0,4.5-7,9-7,9Z"/><path fill="context-fill" d="M8,6S8,2,4.5,2,1,5,1,6c0,4.5,7,9,7,9L9,9Z"/></svg> diff --git a/browser/extensions/onboarding/content/img/icons_library.svg b/browser/extensions/onboarding/content/img/icons_library.svg deleted file mode 100644 index 064c2e6194867..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_library.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?><svg width="92px" height="92px" viewBox="0 0 92 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Tip / Icon / Library</title><desc>Created with Sketch.</desc><defs></defs><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Tip-/-Icon-/-Library" fill-rule="nonzero" fill="#0C0C0D"><g id="Icon-/-Library-/-Web"><path d="M28.7405828,17.2350375 C25.5662458,17.2350375 22 [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_no-icon.png b/browser/extensions/onboarding/content/img/icons_no-icon.png new file mode 100644 index 0000000000000..21aae225793b3 Binary files /dev/null and b/browser/extensions/onboarding/content/img/icons_no-icon.png differ diff --git a/browser/extensions/onboarding/content/img/icons_performance.svg b/browser/extensions/onboarding/content/img/icons_performance.svg deleted file mode 100644 index ad23ba27400ca..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_performance.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M8 1a8.009 8.009 0 0 0-8 8 7.917 7.917 0 0 0 .78 3.43 1 1 0 1 0 1.8-.86A5.943 5.943 0 0 1 2 9a6 6 0 1 1 11.414 2.571 1 1 0 1 0 1.807.858A7.988 7.988 0 0 0 8 1z"/><path fill="context-fill" d="M11.769 7.078a.5.5 0 0 0-.69.153L8.616 11.1a2 2 0 1 0 .5 3.558 2.011 2.011 0 0 0 .54-.54 1.954 1.954 0 0 0-.2-2.479l2.463-3.871a.5.5 0 0 0-.15-.69z"/></svg> diff --git a/browser/extensions/onboarding/content/img/icons_private.svg b/browser/extensions/onboarding/content/img/icons_private.svg deleted file mode 100755 index 7d4d2c416801c..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_private.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16"><title>Icons / Private Browsing</title><g fill="none"><path d="M0 0h32v32H0z"/><path d="M20.4 20c-1.7 0-2.8-2-4.4-2-1.6 0-2.8 2-4.4 2-2 0-3.5-2-3.5-5.3-.1-2 .6-2.7 3.2-2.7s3.4 1.1 4.7 1.1c1.3 0 2.1-1.1 4.7-1.1s3.3.7 3.2 2.7c0 3.3-1.5 5.3-3.5 5.3zm-7.8-5.4c-1.6 0-2.3 1-2.3 1.2 0 .3 1.1.9 2.1.9 1.1 0 2.3-.4 2.3-.7-.2-1-1.1-1.6-2.1-1.4zm6.8 0c-1-.2-1.9.4-2.1 1.4 0 .3 1.2.7 2.3.7 1 0 2.1-.6 2.1-.9 0-.2-.7-1.2- [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_screenshots.svg b/browser/extensions/onboarding/content/img/icons_screenshots.svg deleted file mode 100644 index 8d219dce78b5e..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_screenshots.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?><svg width="92px" height="92px" viewBox="0 0 92 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Tip / Icon / Screenshots</title><desc>Created with Sketch.</desc><defs></defs><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Tip-/-Icon-/-Screenshots" fill-rule="nonzero" fill="#0C0C0D"><g id="Icon-/-Screenshot-/-Web"><path d="M23.0526905,5.75 C16.7062659,5.75 11. [...] \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_singlesearch.svg b/browser/extensions/onboarding/content/img/icons_singlesearch.svg deleted file mode 100644 index 3e06a38522881..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_singlesearch.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16 "><title>Icons / Search</title><g fill="none"><path d="M0 0h32v32H0z"/><path d="M23.7 22.3l-4.8-4.8c1.8-2.5 1.4-6.1-1-8.1s-5.9-1.9-8.1.4c-2.3 2.2-2.4 5.7-.4 8.1 2 2.4 5.6 2.8 8.1 1l4.8 4.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4zM14 18c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4c0 1.1-.4 2.1-1.1 2.9-.8.7-1.8 1.1-2.9 1.1z" fill="#3E3D40"/></g></svg> \ No newline at end of file diff --git a/browser/extensions/onboarding/content/img/icons_sync.svg b/browser/extensions/onboarding/content/img/icons_sync.svg deleted file mode 100644 index 286422275aa7a..0000000000000 --- a/browser/extensions/onboarding/content/img/icons_sync.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="8 8 16 16"><title> Icons / Sync</title><desc> Created with Sketch.</desc><g fill="none"><rect width="32" height="32"/><path d="M22 9C21.4 9 21 9.4 21 10L21 11.1C19.2 9.3 16.6 8.6 14.2 9.2 11.7 9.9 9.8 11.8 9.2 14.3 9.1 14.7 9.2 15 9.5 15.3 9.8 15.5 10.1 15.6 10.5 15.5 10.8 15.4 11.1 15.1 11.2 14.8 11.7 12.6 13.7 11 16 11 17.6 11 19 11.7 20 13L18 13C17.4 13 17 13.4 17 14 17 14.6 17.4 15 18 15L22 15C22.6 15 23 1 [...] diff --git a/browser/extensions/onboarding/content/img/icons_tour-complete.png b/browser/extensions/onboarding/content/img/icons_tour-complete.png new file mode 100644 index 0000000000000..8802bf083ed3c Binary files /dev/null and b/browser/extensions/onboarding/content/img/icons_tour-complete.png differ diff --git a/browser/extensions/onboarding/content/img/icons_tour-complete.svg b/browser/extensions/onboarding/content/img/icons_tour-complete.svg index 173e72c332df3..761c31cbf9d09 100644 --- a/browser/extensions/onboarding/content/img/icons_tour-complete.svg +++ b/browser/extensions/onboarding/content/img/icons_tour-complete.svg @@ -8,10 +8,10 @@ <g id="Tips-/-Navigation" transform="translate(-30.000000, -117.000000)" stroke-width="2"> <g id="Group"> <g id="Tip-/-Check" transform="translate(30.000000, 117.000000)"> - <circle id="Oval-2" stroke="#FFFFFF" fill="#33F70C" fill-rule="evenodd" cx="10" cy="10" r="9"></circle> + <circle id="Oval-2" stroke="#FFFFFF" fill="#00DDB3" fill-rule="evenodd" cx="10" cy="10" r="9"></circle> <polyline id="Path-31" stroke="#165866" stroke-linecap="round" stroke-linejoin="round" points="5.5 10.5 8.5 13.5 14.5 6.5"></polyline> </g> </g> </g> </g> -</svg> \ No newline at end of file +</svg> diff --git a/browser/extensions/onboarding/content/img/watermark.svg b/browser/extensions/onboarding/content/img/watermark.svg deleted file mode 100644 index c9345ed2ba1df..0000000000000 --- a/browser/extensions/onboarding/content/img/watermark.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><title>newtab-firefox-gry</title><path d="M31.359,14.615h0c-.044-.289-.088-.459-.088-.459s-.113.131-.3.378A10.77,10.77,0,0,0,30.6,12.5a13.846,13.846,0,0,0-.937-2.411,10.048,10.048,0,0,0-.856-1.468q-.176-.263-.359-.51c-.57-.931-1.224-1.5-1.981-2.576a7.806,7.806,0,0,1-.991-2.685A10.844,10.844,0,0,0,25,4.607c-.777-.784-1.453-1.341-1.861-1.721C21.126,1.006,21.36.031,21.36.031h0S17.6,4.228,19.229,8.6a8.4,8.4,0, [...] diff --git a/browser/extensions/onboarding/content/onboarding-tor-circuit-display.js b/browser/extensions/onboarding/content/onboarding-tor-circuit-display.js new file mode 100644 index 0000000000000..de4b23c84c2a4 --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding-tor-circuit-display.js @@ -0,0 +1,283 @@ +// Copyright (c) 2018, The Tor Project, Inc. +// vim: set sw=2 sts=2 ts=8 et syntax=javascript: + +let gStringBundle; + +let domLoadedListener = (aEvent) => { + let doc = aEvent.originalTarget; + if (doc.nodeName == "#document") { + removeEventListener("DOMContentLoaded", domLoadedListener); + beginCircuitDisplayOnboarding(); + } +}; + +addEventListener("DOMContentLoaded", domLoadedListener, false); + +function beginCircuitDisplayOnboarding() { + // 1 of 3: Show the introductory "How do circuits work?" info panel. + let target = "torBrowser-circuitDisplay"; + let title = getStringFromName("intro.title"); + let msg = getStringFromName("intro.msg"); + let button1Label = getStringFromName("one-of-three"); + let button2Label = getStringFromName("next"); + let buttons = []; + buttons.push({label: button1Label, style: "text"}); + buttons.push({label: button2Label, style: "primary", callback: function() { + showCircuitDiagram(); }}); + let options = {closeButtonCallback: function() { cleanUp(); }}; + Mozilla.UITour.showInfo(target, title, msg, undefined, buttons, options); +} + +function showCircuitDiagram() { + // 2 of 3: Open the control center and show the circuit diagram info panel. + Mozilla.UITour.showMenu("controlCenter", function() { + let target = "torBrowser-circuitDisplay-diagram"; + let title = getStringFromName("diagram.title"); + let msg = getStringFromName("diagram.msg"); + let button1Label = getStringFromName("two-of-three"); + let button2Label = getStringFromName("next"); + let buttons = []; + buttons.push({label: button1Label, style: "text"}); + buttons.push({label: button2Label, style: "primary", callback: function() { + showNewCircuitButton(); }}); + let options = {closeButtonCallback: function() { cleanUp(); }}; + Mozilla.UITour.showInfo(target, title, msg, undefined, buttons, options); + }); +} + +function showNewCircuitButton() { + // 3 of 3: Show the New Circuit button info panel. + let target = "torBrowser-circuitDisplay-newCircuitButton"; + let title = getStringFromName("new-circuit.title"); + let msg = getStringFromName("new-circuit.msg"); + let button1Label = getStringFromName("three-of-three"); + let button2Label = getStringFromName("done"); + let buttons = []; + buttons.push({label: button1Label, style: "text"}); + buttons.push({label: button2Label, style: "primary", callback: function() { + cleanUp(); }}); + let options = {closeButtonCallback: function() { cleanUp(); }}; + Mozilla.UITour.showInfo(target, title, msg, undefined, buttons, options); +} + +function cleanUp() { + Mozilla.UITour.hideMenu("controlCenter"); + Mozilla.UITour.closeTab(); +} + +function getStringFromName(aName) { + const TORBUTTON_BUNDLE_URI = "chrome://torbutton/locale/browserOnboarding.properties"; + const PREFIX = "onboarding.tor-circuit-display."; + + if (!gStringBundle) { + gStringBundle = Services.strings.createBundle(TORBUTTON_BUNDLE_URI) + } + + let result; + try { + result = gStringBundle.GetStringFromName(PREFIX + aName); + } catch (e) { + result = aName; + } + return result; +} + + +// The remainder of the code in this file was adapted from +// browser/components/uitour/UITour-lib.js (unfortunately, we cannot use that +// code here because it directly accesses 'document' and it assumes that the +// content window is the global JavaScript object), + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// create namespace +if (typeof Mozilla == "undefined") { + var Mozilla = {}; +} + +(function($) { + "use strict"; + + // create namespace + if (typeof Mozilla.UITour == "undefined") { + /** + * Library that exposes an event-based Web API for communicating with the + * desktop browser chrome. It can be used for tasks such as opening menu + * panels and highlighting the position of buttons in the toolbar. + * + * <p>For security/privacy reasons `Mozilla.UITour` will only work on a list of allowed + * secure origins. The list of allowed origins can be found in + * {@link https://dxr.mozilla.org/mozilla-central/source/browser/app/permissions| + * browser/app/permissions}.</p> + * + * @since 29 + * @namespace + */ + Mozilla.UITour = {}; + } + + function _sendEvent(action, data) { + var event = new content.CustomEvent("mozUITour", { + bubbles: true, + detail: { + action, + data: data || {} + } + }); + + content.document.dispatchEvent(event); + } + + function _generateCallbackID() { + return Math.random().toString(36).replace(/[^a-z]+/g, ""); + } + + function _waitForCallback(callback) { + var id = _generateCallbackID(); + + function listener(event) { + if (typeof event.detail != "object") + return; + if (event.detail.callbackID != id) + return; + + content.document.removeEventListener("mozUITourResponse", listener); + callback(event.detail.data); + } + content.document.addEventListener("mozUITourResponse", listener); + + return id; + } + + /** + * Show an arrow panel with optional images and buttons anchored at a specific UI target. + * + * @see Mozilla.UITour.hideInfo + * + * @param {Mozilla.UITour.Target} target - Identifier of the UI widget to anchor the panel at. + * @param {String} title - Title text to be shown as the heading of the panel. + * @param {String} text - Body text of the panel. + * @param {String} [icon=null] - URL of a 48x48px (96px @ 2dppx) image (which will be resolved + * relative to the tab's URI) to display in the panel. + * @param {Object[]} [buttons=[]] - Array of objects describing buttons. + * @param {String} buttons[].label - Button label + * @param {String} buttons[].icon - Button icon URL + * @param {String} buttons[].style - Button style ("primary" or "link") + * @param {Function} buttons[].callback - Called when the button is clicked + * @param {Object} [options={}] - Advanced options + * @param {Function} options.closeButtonCallback - Called when the panel's close button is clicked. + * + * @example + * var buttons = [ + * { + * label: 'Cancel', + * style: 'link', + * callback: cancelBtnCallback + * }, + * { + * label: 'Confirm', + * style: 'primary', + * callback: confirmBtnCallback + * } + * ]; + * + * var icon = '//mozorg.cdn.mozilla.net/media/img/firefox/australis/logo.png'; + * + * var options = { + * closeButtonCallback: closeBtnCallback + * }; + * + * Mozilla.UITour.showInfo('appMenu', 'my title', 'my text', icon, buttons, options); + */ + Mozilla.UITour.showInfo = function(target, title, text, icon, buttons, options) { + var buttonData = []; + if (Array.isArray(buttons)) { + for (var i = 0; i < buttons.length; i++) { + buttonData.push({ + label: buttons[i].label, + icon: buttons[i].icon, + style: buttons[i].style, + callbackID: _waitForCallback(buttons[i].callback) + }); + } + } + + var closeButtonCallbackID, targetCallbackID; + if (options && options.closeButtonCallback) + closeButtonCallbackID = _waitForCallback(options.closeButtonCallback); + if (options && options.targetCallback) + targetCallbackID = _waitForCallback(options.targetCallback); + + _sendEvent("showInfo", { + target, + title, + text, + icon, + buttons: buttonData, + closeButtonCallbackID, + targetCallbackID + }); + }; + + /** + * Hide any visible info panels. + * @see Mozilla.UITour.showInfo + */ + Mozilla.UITour.hideInfo = function() { + _sendEvent("hideInfo"); + }; + + /** + * Open the named application menu. + * + * @see Mozilla.UITour.hideMenu + * + * @param {Mozilla.UITour.MenuName} name - Menu name + * @param {Function} [callback] - Callback to be called with no arguments when + * the menu opens. + * + * @example + * Mozilla.UITour.showMenu('appMenu', function() { + * console.log('menu was opened'); + * }); + */ + Mozilla.UITour.showMenu = function(name, callback) { + var showCallbackID; + if (callback) + showCallbackID = _waitForCallback(callback); + + _sendEvent("showMenu", { + name, + showCallbackID, + }); + }; + + /** + * Close the named application menu. + * + * @see Mozilla.UITour.showMenu + * + * @param {Mozilla.UITour.MenuName} name - Menu name + */ + Mozilla.UITour.hideMenu = function(name) { + _sendEvent("hideMenu", { + name + }); + }; + + /** + * @summary Closes the tab where this code is running. As usual, if the tab is in the + * foreground, the tab that was displayed before is selected. + * + * @description The last tab in the current window will never be closed, in which case + * this call will have no effect. The calling code is expected to take an + * action after a small timeout in order to handle this case, for example by + * displaying a goodbye message or a button to restart the tour. + * @since 46 + */ + Mozilla.UITour.closeTab = function() { + _sendEvent("closeTab"); + }; +})(); diff --git a/browser/extensions/onboarding/content/onboarding-tour-agent.js b/browser/extensions/onboarding/content/onboarding-tour-agent.js index d60a41b2c9f58..7cdb10063f282 100644 --- a/browser/extensions/onboarding/content/onboarding-tour-agent.js +++ b/browser/extensions/onboarding/content/onboarding-tour-agent.js @@ -18,6 +18,18 @@ let onCanSetDefaultBrowserInBackground = () => {
let onClick = evt => { switch (evt.target.id) { + case "onboarding-tour-tor-security-button": + Mozilla.UITour.torBrowserOpenSecurityLevelPanel(); + break; + case "onboarding-tour-tor-toolbar-update-9-0-button": + Mozilla.UITour.showHighlight("torBrowser-newIdentityButton", "zoom"); + break; + case "onboarding-tour-tor-network-action-button": + Mozilla.UITour.openPreferences("tor"); + break; +#if 0 +// Firefox onboarding actions. To reduce conflicts when rebasing against +// newer Firefox code, we use the preprocessor to omit this code block. case "onboarding-tour-addons-button": Mozilla.UITour.showHighlight("addons"); break; @@ -60,6 +72,7 @@ let onClick = evt => { case "onboarding-tour-sync-connect-device-button": Mozilla.UITour.showConnectAnotherDevice(); break; +#endif } let classList = evt.target.classList; // On keyboard navigation the target would be .onboarding-tour-item. diff --git a/browser/extensions/onboarding/content/onboarding.css b/browser/extensions/onboarding/content/onboarding.css index 8f24314776341..e801790bf5a85 100644 --- a/browser/extensions/onboarding/content/onboarding.css +++ b/browser/extensions/onboarding/content/onboarding.css @@ -14,8 +14,8 @@ /* Ensuring we can put the overlay over elements using z-index on original page */ z-index: 20999; - color: #4d4d4d; - background: var(--newtab-overlay-color, rgb(245, 245, 247, 0.9)); /* #f7f7f5, 0.9 opacity */ + color: #4a4a4a; + background: rgba(0,0,0,0); display: none; }
@@ -23,12 +23,45 @@ display: block; }
-#onboarding-overlay-button { - padding: 10px 0 0 0; +#onboarding-overlay-button-container { + padding: 16px 0 0 0; position: fixed; - cursor: pointer; top: 4px; inset-inline-start: 12px; +} + +/* + * Define an animated attention-grabbing dot which is shown on the + * speech bubble when we are displaying the "updated" tour. +*/ +#onboarding-overlay-button-container.onboarding-overlay-attention-dot::after { + display: inline-block; + position: relative; + content: " "; + width: 20px; + height: 20px; + top: -8px; + inset-inline-start: -16px; + background-color: #00E2B1; + border-radius: 50%; + animation: pulsate 2.0s ease-out; + animation-iteration-count: 7; +} + +@keyframes pulsate { + 0% { + opacity: 1.0; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 1.0; + } +} + +#onboarding-overlay-button { + cursor: pointer; border: none; /* Set to none so no grey contrast background in the high-contrast mode */ background: none; @@ -56,7 +89,7 @@ margin-top: -1px; margin-inline-start: -13px; border: 2px solid #f2f2f2; - background: #0A84FF; + background: #420c5d; padding: 0; width: 10px; height: 10px; @@ -70,7 +103,7 @@
#onboarding-overlay-button:hover::after, #onboarding-overlay-button.onboarding-speech-bubble::after { - background: #0060df; + background: rgba(255,255,255,0.2); font-size: 13px; text-align: center; color: #fff; @@ -78,7 +111,7 @@ font-weight: 400; content: attr(aria-label); border: 1px solid transparent; - border-radius: 2px; + border-radius: 12px; padding: 10px 16px; width: auto; height: auto; @@ -94,21 +127,6 @@ box-shadow: 2px 0 5px 0 rgba(74, 74, 79, 0.25); }
-#onboarding-overlay-button-watermark-icon { - -moz-context-properties: fill; - fill: var(--newtab-icon-tertiary-color, #d7d7db); -} - -#onboarding-overlay-button-watermark-icon, -#onboarding-overlay-button.onboarding-watermark::after, -#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-icon { - display: none; -} - -#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-watermark-icon { - display: block; -} - #onboarding-overlay-dialog, .onboarding-hidden, #onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out, @@ -124,24 +142,17 @@ width: 16px; height: 16px; border: none; - background: none; + background: url("img/close.png") center no-repeat; padding: 0; }
-.onboarding-close-btn::before { - content: url("chrome://global/skin/icons/close.svg"); - -moz-context-properties: fill, fill-opacity; - fill-opacity: 0; - fill: var(--newtab-icon-primary-color, currentColor); -} - -.onboarding-close-btn:-moz-any(:hover, :active, :focus, :-moz-focusring)::before { - fill-opacity: 0.1; +.onboarding-close-btn:-moz-any(:hover, :active, :focus, :-moz-focusring) { + background-color: rgba(0, 0, 0, 0.1); }
#onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog { width: 960px; - height: 510px; + height: 540px; background: #fff; border: 1px solid rgba(9, 6, 13, 0.2); /* #09060D, 0.2 opacity */ border-radius: 3px; @@ -218,7 +229,7 @@ font-size: 16px; cursor: pointer; max-height: 54px; - --onboarding-tour-item-active-color: #0A84FF; + --onboarding-tour-item-active-color: #420c5d; }
#onboarding-tour-list .onboarding-tour-item:dir(rtl) { @@ -226,7 +237,7 @@ }
#onboarding-tour-list .onboarding-tour-item.onboarding-complete::before { - content: url("img/icons_tour-complete.svg"); + content: url("img/icons_tour-complete.png"); position: relative; inset-inline-start: 3px; top: -10px; @@ -262,6 +273,7 @@
#onboarding-tour-list .onboarding-tour-item.onboarding-active, #onboarding-tour-list .onboarding-tour-item-container:hover .onboarding-tour-item { + font-weight: bold; color: var(--onboarding-tour-item-active-color); /* With 1px transparent outline, could see a border in the high-constrast mode */ outline: 1px solid transparent; @@ -319,6 +331,18 @@ grid-template-columns: [tour-page-start] 368px [tour-content-start] 1fr [tour-page-end]; }
+.onboarding-tour-description-highlight { + display: inline-block; + margin-inline-start: 8px; + padding: 6px 8px; + vertical-align: middle; + background-color: #F1F1F3; + border-radius: 4px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} + .onboarding-tour-description { grid-row: tour-page-start / tour-page-end; grid-column: tour-page-start / tour-content-start; @@ -326,15 +350,26 @@ line-height: 22px; padding-inline-start: 40px; padding-inline-end: 28px; - max-height: 360px; + max-height: 370px; overflow: auto; }
.onboarding-tour-description > h1 { - font-size: 36px; - margin-top: 16px; + font-size: 30px; + margin: 16px 0px 10px 0px; font-weight: 300; - line-height: 44px; + line-height: 36px; + color: #420c5d; +} + +.onboarding-tour-description-para2 { + margin-top: 16px; +} + +.onboarding-tour-description-suffix { + margin-top: 6px; + font-size: 13px; + line-height: 16px; }
.onboarding-tour-content { @@ -345,8 +380,8 @@ }
.onboarding-tour-content > img { - width: 352px; - margin: 0; + width: 300px; + margin: 20px; }
/* These illustrations need to be stuck on the right side to the border. Thus we @@ -369,7 +404,7 @@ }
.onboarding-tour-action-button { - background: #0060df; + background: #4d0c5d; /* With 1px transparent border, could see a border in the high-constrast mode */ border: 1px solid transparent; border-radius: 2px; @@ -399,18 +434,43 @@ }
.onboarding-tour-action-button:hover:not([disabled]) { - background: #003eaa; + background: #410a4e; cursor: pointer; }
.onboarding-tour-action-button:active:not([disabled]) { - background: #002275; + background: #34083f; }
.onboarding-tour-action-button:disabled { opacity: 0.5; }
+/* Tor action buttons appear in the description column rather than the content one. */ +.onboarding-tour-tor-action-button-container { + /* Get higher z-index in order to ensure buttons within container are selectable */ + z-index: 2; + grid-row: tour-button-start / tour-page-end; + grid-column: tour-page-start / tour-content-start; +} + +.onboarding-tour-tor-action-button-container > .onboarding-tour-action-button { + margin-inline-start: 40px; /* match .onboarding-tour-description */ + float: inline-start; + background: #e6e6e6; + color: #303030; +} + +.onboarding-tour-tor-action-button-container > .onboarding-tour-action-button:hover:not([disabled]) { + background: #d6d6d6; + cursor: pointer; +} + +.onboarding-tour-tor-action-button-container > .onboarding-tour-action-button:active:not([disabled]) { + background: #c6c6c6; +} + + /* Tour Icons */ #onboarding-tour-singlesearch.onboarding-tour-item::after, #onboarding-notification-bar[data-target-tour-id=onboarding-tour-singlesearch] #onboarding-notification-tour-title::before { @@ -457,6 +517,15 @@ mask-image: url("img/icons_screenshots.svg"); }
+a#onboarding-tour-tor-expect-differences-button, +a#onboarding-tour-tor-expect-differences-button:hover, +a#onboarding-tour-tor-expect-differences-button:visited, +a#onboarding-tour-tor-onion-services-button, +a#onboarding-tour-tor-onion-services-button:hover, +a#onboarding-tour-tor-onion-services-button:visited, +a#onboarding-tour-tor-learn-more-button, +a#onboarding-tour-tor-learn-more-button:hover, +a#onboarding-tour-tor-learn-more-button:visited, a#onboarding-tour-screenshots-button, a#onboarding-tour-screenshots-button:hover, a#onboarding-tour-screenshots-button:visited { @@ -464,6 +533,12 @@ a#onboarding-tour-screenshots-button:visited { text-decoration: none; }
+/* The Tor Browswer tour items do not have icons, so we use a transparent PNG. */ +.onboarding-tour-item::after, +#onboarding-notification-bar[data-target-tour-id] #onboarding-notification-tour-title::before { + mask-image: url("img/icons_no-icon.png"); +} + /* Tour Notifications */ #onboarding-notification-bar { position: fixed; diff --git a/browser/extensions/onboarding/content/onboarding.js b/browser/extensions/onboarding/content/onboarding.js index fd4275a14072c..db808278a4e04 100644 --- a/browser/extensions/onboarding/content/onboarding.js +++ b/browser/extensions/onboarding/content/onboarding.js @@ -12,6 +12,7 @@ ChromeUtils.defineModuleGetter(this, "Onboarding", "resource://onboarding/Onboar const ABOUT_HOME_URL = "about:home"; const ABOUT_NEWTAB_URL = "about:newtab"; const ABOUT_WELCOME_URL = "about:welcome"; +const ABOUT_TOR_URL = "about:tor";
// Load onboarding module only when we enable it. if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) { @@ -22,7 +23,7 @@ if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
let window = evt.target.defaultView; let location = window.location.href; - if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL || location == ABOUT_WELCOME_URL) { + if (location == ABOUT_TOR_URL) { // We just want to run tests as quickly as possible // so in the automation test, we don't do `requestIdleCallback`. if (Cu.isInAutomation) { diff --git a/browser/extensions/onboarding/jar.mn b/browser/extensions/onboarding/jar.mn index 1d580be9861f9..af83e1d06e6ca 100644 --- a/browser/extensions/onboarding/jar.mn +++ b/browser/extensions/onboarding/jar.mn @@ -6,9 +6,14 @@ # resource://onboarding/ is referenced in about:home about:newtab and about:welcome, # so make it content-accessible. % resource onboarding %content/ contentaccessible=yes - content/ (content/*) + content/ (content/*.css) + content/img/ (content/img/*) +* content/onboarding-tour-agent.js (content/onboarding-tour-agent.js) + content/onboarding.js (content/onboarding.js) +* content/Onboarding.jsm (content/Onboarding.jsm) + content/onboarding-tor-circuit-display.js (content/onboarding-tor-circuit-display.js) # Package UITour-lib.js in here rather than under # /browser/components/uitour to avoid "unreferenced files" error when # Onboarding extension is not built. content/lib/UITour-lib.js (/browser/components/uitour/UITour-lib.js) - content/modules/ (*.jsm) + content/modules/OnboardingTourType.jsm (OnboardingTourType.jsm) diff --git a/browser/extensions/onboarding/moz.build b/browser/extensions/onboarding/moz.build index 4756afe507fbe..a5a4b99a4712e 100644 --- a/browser/extensions/onboarding/moz.build +++ b/browser/extensions/onboarding/moz.build @@ -13,12 +13,15 @@ DEFINES["MOZ_APP_MAXVERSION"] = CONFIG["MOZ_APP_MAXVERSION"] DIRS += ["locales"]
FINAL_TARGET_FILES.features["onboarding@mozilla.org"] += [ - "api.js", "background.js", "manifest.json", "schema.json", ]
+FINAL_TARGET_PP_FILES.features["onboarding@mozilla.org"] += [ + "api.js", +] + BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"]
XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"] diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index a14abba4c2097..03ddd497bb2df 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -355,15 +355,6 @@ menuitem.bookmark-item { margin-inline-end: -4px; }
-/** - * Override the --arrowpanel-padding so the background extends - * to the sides and bottom of the panel. - */ -#UITourTooltipButtons { - margin-inline-start: -10px; - margin-bottom: -10px; -} - %include ../shared/contextmenu.inc.css
#context-navigation > .menuitem-iconic > .menu-iconic-left { diff --git a/browser/themes/shared/UITour.inc.css b/browser/themes/shared/UITour.inc.css index f47a9db8cfc4c..730d47ce13af5 100644 --- a/browser/themes/shared/UITour.inc.css +++ b/browser/themes/shared/UITour.inc.css @@ -39,7 +39,6 @@
#UITourTooltipTitleContainer { -moz-box-align: start; - margin-bottom: 10px; }
#UITourTooltipIcon { @@ -54,15 +53,25 @@ }
#UITourTooltipTitle { - font-size: 1.45rem; - font-weight: bold; + font-size: 1.25em; + font-weight: 600; margin: 0; }
+#UITourTooltipToolbarSeparator { + appearance: none; + min-height: 0; + border-top: 1px solid var(--panel-separator-color); + border-bottom: none; + margin: var(--panel-separator-margin); + margin-inline: 0; + padding: 0; +} + #UITourTooltipDescription { margin-inline: 0; - font-size: 1.15rem; - line-height: 1.8rem; + font-size: 1.11em; + line-height: normal; margin-bottom: 0; /* Override global.css */ }
@@ -82,10 +91,7 @@
#UITourTooltipButtons { -moz-box-pack: end; - background-color: var(--arrowpanel-dimmed); - border-top: 1px solid var(--panel-separator-color); - margin: 10px -16px -16px; - padding: 16px; + padding-block-start: 16px; }
#UITourTooltipButtons > label, @@ -111,25 +117,14 @@
#UITourTooltipButtons > label, #UITourTooltipButtons > button .button-text { - font-size: 1.15rem; + font-weight: 600; + margin-inline: 0; }
#UITourTooltipButtons > button:not(.button-link) { appearance: none; - background-color: rgb(251,251,251); - border-radius: 3px; - border: 1px solid; - border-color: rgb(192,192,192); - color: rgb(71,71,71); - padding: 4px 30px; - transition-property: background-color, border-color; - transition-duration: 150ms; -} - -#UITourTooltipButtons > button:not(.button-link, :active):hover { - background-color: hsla(210,4%,10%,.15); - border-color: hsla(210,4%,10%,.15); - box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset; + border-radius: 4px; + padding: 8px 16px; }
#UITourTooltipButtons > label, @@ -138,17 +133,18 @@ background: transparent; border: none; box-shadow: none; - color: var(--panel-disabled-color); padding-inline: 10px; }
-/* The primary button gets the same color as the customize button. */ #UITourTooltipButtons > button.button-primary { - background-color: rgb(116,191,67); - color: white; - padding-inline: 30px; + background-color: var(--button-primary-bgcolor); + color: var(--button-primary-color); +} + +#UITourTooltipButtons > button.button-primary:active { + background-color: var(--button-primary-active-bgcolor); }
#UITourTooltipButtons > button.button-primary:not(:active):hover { - background-color: rgb(105,173,61); + background-color: var(--button-primary-hover-bgcolor); } diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index 7f4eb881cc9e6..b60e9143fc866 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -624,15 +624,6 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
%include ../shared/UITour.inc.css
-#UITourTooltipButtons { - /** - * Override the --arrowpanel-padding so the background extends - * to the sides and bottom of the panel. - */ - margin-inline: -10px; - margin-bottom: -10px; -} - %include ../shared/contextmenu.inc.css
/* Make menu items larger when opened through touch. */ diff --git a/intl/strres/nsStringBundle.cpp b/intl/strres/nsStringBundle.cpp index 7579ae9b2e38a..7d0e2df12857e 100644 --- a/intl/strres/nsStringBundle.cpp +++ b/intl/strres/nsStringBundle.cpp @@ -78,6 +78,7 @@ static const char kContentBundles[][52] = { "chrome://global/locale/svg/svg.properties", "chrome://global/locale/xul.properties", "chrome://necko/locale/necko.properties", + "chrome://torbutton/locale/onboarding.properties", };
static bool IsContentBundle(const nsCString& aUrl) {
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit c197e8caaac0ae246e17107b523187e7304fcf34 Author: Alex Catarineu acat@torproject.org AuthorDate: Sun Aug 2 19:12:25 2020 +0200
Bug 40069: Add helpers for message passing with extensions --- toolkit/components/extensions/ExtensionParent.jsm | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+)
diff --git a/toolkit/components/extensions/ExtensionParent.jsm b/toolkit/components/extensions/ExtensionParent.jsm index 39ce6d608b861..32d264ed6a4f3 100644 --- a/toolkit/components/extensions/ExtensionParent.jsm +++ b/toolkit/components/extensions/ExtensionParent.jsm @@ -263,6 +263,8 @@ const ProxyMessenger = { /** @type Map<number, ParentPort> */ ports: new Map(),
+ _torRuntimeMessageListeners: [], + init() { this.conduit = new BroadcastConduit(ProxyMessenger, { id: "ProxyMessenger", @@ -328,6 +330,10 @@ const ProxyMessenger = { },
async recvRuntimeMessage(arg, { sender }) { + // We need to listen to some extension messages in Tor Browser + for (const listener of this._torRuntimeMessageListeners) { + listener(arg); + } arg.firstResponse = true; let kind = await this.normalizeArgs(arg, sender); let result = await this.conduit.castRuntimeMessage(kind, arg); @@ -1881,6 +1887,45 @@ for (let name of StartupCache.STORE_NAMES) { StartupCache[name] = new CacheStore(name); }
+async function torSendExtensionMessage(extensionId, message) { + // This should broadcast the message to all children "conduits" + // listening for a "RuntimeMessage". Those children conduits + // will either be extension background pages or other extension + // pages listening to browser.runtime.onMessage. + const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", { + extensionId, + holder: new StructuredCloneHolder(message), + firstResponse: true, + sender: { + id: extensionId, + envType: "addon_child", + }, + }); + return result + ? result.value + : Promise.reject({ message: ERROR_NO_RECEIVERS }); +} + +async function torWaitForExtensionMessage(extensionId, checker) { + return new Promise(resolve => { + const msgListener = msg => { + try { + if (msg && msg.extensionId === extensionId) { + const deserialized = msg.holder.deserialize({}); + if (checker(deserialized)) { + const idx = ProxyMessenger._torRuntimeMessageListeners.indexOf( + msgListener + ); + ProxyMessenger._torRuntimeMessageListeners.splice(idx, 1); + resolve(deserialized); + } + } + } catch (e) {} + }; + ProxyMessenger._torRuntimeMessageListeners.push(msgListener); + }); +} + var ExtensionParent = { GlobalManager, HiddenExtensionPage, @@ -1892,6 +1937,8 @@ var ExtensionParent = { promiseExtensionViewLoaded, watchExtensionProxyContextLoad, DebugUtils, + torSendExtensionMessage, + torWaitForExtensionMessage, };
// browserPaintedPromise and browserStartupPromise are promises that
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 9c69aa96a150fcb52440461568fa159faadc5649 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Mon May 6 15:51:06 2013 -0700
TB3: Tor Browser's official .mozconfigs.
Also: Bug #9829.1: new .mozconfig file for the new cross-compiler and ESR24 Changes needed to build Mac in 64bit Bug 10715: Enable Webgl for mingw-w64 again. Disable ICU when cross-compiling; clean-up. Bug 15773: Enable ICU on OS X Bug 15990: Don't build the sandbox with mingw-w64 Bug 12761: Switch to ESR 38 for OS X Updating .mozconfig-asan Bug 12516: Compile hardenend Tor Browser with -fwrapv Bug 18331: Switch to Mozilla's toolchain for building Tor Browser for OS X Bug 17858: Cannot create incremental MARs for hardened builds. Define HOST_CFLAGS, etc. to avoid compiling programs such as mbsdiff (which is part of mar-tools and is not distributed to end-users) with ASan. Bug 13419: Add back ICU for Windows Bug 21239: Use GTK2 for ESR52 Linux builds Bug 23025: Add hardening flags for macOS Bug 24478: Enable debug assertions and tests in our ASan builds --enable-proxy-bypass-protection Bug 27597: ASan build option in tor-browser-build is broken
Bug 27623 - Export MOZILLA_OFFICIAL during desktop builds
This fixes a problem where some preferences had the wrong default value. Also see bug 27472 where we made a similar fix for Android.
Bug 30463: Explicitly disable MOZ_TELEMETRY_REPORTING
Bug 31450: Set proper BINDGEN_CFLAGS for ASan builds
Add an --enable-tor-browser-data-outside-app-dir configure option
Add --with-tor-browser-version configure option
Bug 21849: Don't allow SSL key logging.
Bug 31457: disable per-installation profiles
The dedicated profiles (per-installation) feature does not interact well with our bundled profiles on Linux and Windows, and it also causes multiple profiles to be created on macOS under TorBrowser-Data.
Bug 31935: Disable profile downgrade protection.
Since Tor Browser does not support more than one profile, disable the prompt and associated code that offers to create one when a version downgrade situation is detected.
Bug 32493: Disable MOZ_SERVICES_HEALTHREPORT
Bug 25741 - TBA: Disable features at compile-time
MOZ_NATIVE_DEVICES for casting and the media player MOZ_TELEMETRY_REPORTING for telemetry MOZ_DATA_REPORTING for all data reporting preferences (crashreport, telemetry, geo)
Bug 25741 - TBA: Add default configure options in dedicated file
Define MOZ_ANDROID_NETWORK_STATE and MOZ_ANDROID_LOCATION
Bug 29859: Disable HLS support for now
Add --disable-tor-launcher build option
Add --enable-tor-browser-update build option
Bug 33734: Set MOZ_NORMANDY to False
Bug 33851: Omit Parental Controls.
Bug 40061: Omit the Windows default browser agent from the build
Bug 40107: Adapt .mozconfig-asan for ESR 78
Bug 40252: Add --enable-rust-simd to our tor-browser mozconfig files
Bug 40793: moved Tor configuration options from old-configure.in to moz.configure --- browser/app/profile/001-base-profile.js | 2 + browser/base/moz.build | 3 ++ browser/config/mozconfigs/base-browser | 2 - browser/config/mozconfigs/tor-browser | 9 ++++ browser/installer/Makefile.in | 8 ++++ browser/moz.configure | 8 ++-- mobile/android/confvars.sh | 9 ++++ mobile/android/geckoview/build.gradle | 1 + mobile/android/moz.configure | 21 ++++++++- mobile/android/torbrowser.configure | 30 ++++++++++++ moz.configure | 81 +++++++++++++++++++++++++++++++++ mozconfig-android-armv7 | 37 +++++++++++++++ mozconfig-linux-i686 | 2 +- mozconfig-linux-x86_64 | 2 +- mozconfig-linux-x86_64-asan | 2 +- mozconfig-linux-x86_64-dev | 6 ++- mozconfig-macos-x86_64 | 4 +- mozconfig-windows-i686 | 2 +- mozconfig-windows-x86_64 | 2 +- security/moz.build | 2 +- security/nss/lib/ssl/Makefile | 2 +- toolkit/modules/AppConstants.jsm | 15 ++++++ toolkit/modules/moz.build | 3 ++ 23 files changed, 236 insertions(+), 17 deletions(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index 3584306e4f865..895f78d31f2a5 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -370,6 +370,8 @@ pref("dom.presentation.receiver.enabled", false); pref("dom.audiochannel.audioCompeting", false); pref("dom.audiochannel.mediaControl", false);
+#expand pref("torbrowser.version", __TOR_BROWSER_VERSION_QUOTED__); + // If we are bundling fonts, whitelist those bundled fonts, and restrict system fonts to a selection.
#ifdef MOZ_BUNDLED_FONTS diff --git a/browser/base/moz.build b/browser/base/moz.build index 4058d6d86fead..ee3bc8028b9e9 100644 --- a/browser/base/moz.build +++ b/browser/base/moz.build @@ -81,6 +81,9 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("windows", "gtk", "cocoa"): if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("windows", "gtk"): DEFINES["MENUBAR_CAN_AUTOHIDE"] = 1
+if CONFIG["TOR_BROWSER_UPDATE"]: + DEFINES["TOR_BROWSER_UPDATE"] = 1 + JAR_MANIFESTS += ["jar.mn"]
GeneratedFile( diff --git a/browser/config/mozconfigs/base-browser b/browser/config/mozconfigs/base-browser index cdc493f473022..1fae066a04a71 100644 --- a/browser/config/mozconfigs/base-browser +++ b/browser/config/mozconfigs/base-browser @@ -30,5 +30,3 @@ ac_add_options --disable-system-policies
# Disable telemetry ac_add_options MOZ_TELEMETRY_REPORTING= - -ac_add_options --with-relative-profile=BaseBrowser/Data/Browser diff --git a/browser/config/mozconfigs/tor-browser b/browser/config/mozconfigs/tor-browser new file mode 100644 index 0000000000000..8969a88aeaf9c --- /dev/null +++ b/browser/config/mozconfigs/tor-browser @@ -0,0 +1,9 @@ +. $topsrcdir/browser/config/mozconfigs/base-browser + +mk_add_options MOZ_APP_DISPLAYNAME="Tor Browser" + +ac_add_options --with-relative-profile=TorBrowser/Data/Browser + +ac_add_options --enable-tor-browser-update + +ac_add_options --with-distribution-id=org.torproject diff --git a/browser/installer/Makefile.in b/browser/installer/Makefile.in index f98964d8a9ebd..d55b373ff488e 100644 --- a/browser/installer/Makefile.in +++ b/browser/installer/Makefile.in @@ -82,6 +82,14 @@ endif endif endif
+ifdef TOR_BROWSER_DISABLE_TOR_LAUNCHER +DEFINES += -DTOR_BROWSER_DISABLE_TOR_LAUNCHER +endif + +ifdef TOR_BROWSER_UPDATE +DEFINES += -DTOR_BROWSER_UPDATE +endif + ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET))) DEFINES += -DMOZ_SHARED_MOZGLUE=1 endif diff --git a/browser/moz.configure b/browser/moz.configure index 8653bcbb165da..5a0b722b915e2 100644 --- a/browser/moz.configure +++ b/browser/moz.configure @@ -5,11 +5,11 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
imply_option("MOZ_PLACES", True) -imply_option("MOZ_SERVICES_HEALTHREPORT", True) +imply_option("MOZ_SERVICES_HEALTHREPORT", False) imply_option("MOZ_SERVICES_SYNC", True) -imply_option("MOZ_DEDICATED_PROFILES", True) -imply_option("MOZ_BLOCK_PROFILE_DOWNGRADE", True) -imply_option("MOZ_NORMANDY", True) +imply_option("MOZ_DEDICATED_PROFILES", False) +imply_option("MOZ_BLOCK_PROFILE_DOWNGRADE", False) +imply_option("MOZ_NORMANDY", False)
with only_when(target_is_linux & compile_environment): option(env="MOZ_NO_PIE_COMPAT", help="Enable non-PIE wrapper") diff --git a/mobile/android/confvars.sh b/mobile/android/confvars.sh index 70e13c85b2581..b2670451ed915 100644 --- a/mobile/android/confvars.sh +++ b/mobile/android/confvars.sh @@ -29,6 +29,15 @@ MOZ_ANDROID_BROWSER_INTENT_CLASS=org.mozilla.gecko.BrowserApp
MOZ_NO_SMART_CARDS=1
+# Adds MIME-type support for raw video MOZ_RAW=1
MOZ_APP_ID={aa3c5121-dab2-40e2-81ca-7ea25febc110} + +### Tor Browser for Android ### + +# Disable telemetry at compile-time +unset MOZ_TELEMETRY_REPORTING + +# Disable data reporting at compile-time +unset MOZ_DATA_REPORTING diff --git a/mobile/android/geckoview/build.gradle b/mobile/android/geckoview/build.gradle index f60ea1730d5c0..bdee206175db2 100644 --- a/mobile/android/geckoview/build.gradle +++ b/mobile/android/geckoview/build.gradle @@ -93,6 +93,7 @@ android { buildConfigField 'String', "MOZ_APP_DISPLAYNAME", ""${mozconfig.substs.MOZ_APP_DISPLAYNAME}""; buildConfigField 'String', "MOZ_APP_UA_NAME", ""${mozconfig.substs.MOZ_APP_UA_NAME}""; buildConfigField 'String', "MOZ_UPDATE_CHANNEL", ""${mozconfig.substs.MOZ_UPDATE_CHANNEL}""; + buildConfigField 'String', "TOR_BROWSER_VERSION", ""${mozconfig.substs.TOR_BROWSER_VERSION}"";
// MOZILLA_VERSION is oddly quoted from autoconf, but we don't have to handle it specially in Gradle. buildConfigField 'String', "MOZILLA_VERSION", ""${mozconfig.substs.MOZILLA_VERSION}""; diff --git a/mobile/android/moz.configure b/mobile/android/moz.configure index 106f6c8168145..96a014bb28e83 100644 --- a/mobile/android/moz.configure +++ b/mobile/android/moz.configure @@ -13,7 +13,7 @@ project_flag( project_flag( "MOZ_ANDROID_HLS_SUPPORT", help="Enable HLS (HTTP Live Streaming) support (currently using the ExoPlayer library)", - default=True, + default=False, )
option( @@ -51,7 +51,10 @@ set_config( )
imply_option("MOZ_NORMANDY", False) -imply_option("MOZ_SERVICES_HEALTHREPORT", True) +# Comment this so we can imply |False| in torbrowser.configure +# The Build system doesn't allow multiple imply_option() +# calls with the same key. +# imply_option("MOZ_SERVICES_HEALTHREPORT", True) imply_option("MOZ_ANDROID_HISTORY", True) imply_option("--enable-small-chunk-size", True)
@@ -70,6 +73,8 @@ def check_target(target): )
+include("torbrowser.configure") + include("../../toolkit/moz.configure") include("../../build/moz.configure/android-sdk.configure") include("../../build/moz.configure/java.configure") @@ -87,3 +92,15 @@ set_config( "MOZ_ANDROID_FAT_AAR_ARCHITECTURES", depends("MOZ_ANDROID_FAT_AAR_ARCHITECTURES")(lambda x: x), ) + +project_flag( + "MOZ_ANDROID_NETWORK_STATE", + help="Include permission for accessing WiFi/network state on Android", + default=False, +) + +project_flag( + "MOZ_ANDROID_LOCATION", + help="Include permission for accessing fine and course-grain Location on Android", + default=False, +) diff --git a/mobile/android/torbrowser.configure b/mobile/android/torbrowser.configure new file mode 100644 index 0000000000000..bcb725cae121a --- /dev/null +++ b/mobile/android/torbrowser.configure @@ -0,0 +1,30 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Set Tor Browser default config + +imply_option("MOZ_ANDROID_EXCLUDE_FONTS", False) + +# Disable uploading crash reports and dump files to an external server +# This is still configured in old-configure. Uncomment when this moves +# to the python config +# imply_option("MOZ_CRASHREPORTER", False) + +# Disable uploading information about the browser configuration and +# performance to an external server +imply_option("MOZ_SERVICES_HEALTHREPORT", False) + +# Disable creating telemetry and data reports that are uploaded to an +# external server +# These aren't actually configure options. These are disabled in +# confvars.sh, but they look like configure options so we'll document +# them here, as well. +# XXX: no confvars.sh here +# imply_option("MOZ_TELEMETRY_REPORTING", False) +# imply_option("MOZ_DATA_REPORTING", False) + +imply_option("MOZ_ANDROID_NETWORK_STATE", False) +imply_option("MOZ_ANDROID_LOCATION", False) diff --git a/moz.configure b/moz.configure index 244a0c775d098..ecac14e9396a6 100755 --- a/moz.configure +++ b/moz.configure @@ -1136,6 +1136,87 @@ def relative_profile(value, target): set_define("RELATIVE_PROFILE_DIRECTORY", relative_profile)
+# Tor additions. + +option( + "--with-tor-browser-version", + nargs=1, + help="Set Tor Browser version, e.g., 7.0a1" +) + + +@depends("--with-tor-browser-version") +def tor_browser_version(value): + if not value: + die("--with-tor-browser-version is required for Tor Browser.") + return value[0] + + +@depends("--with-tor-browser-version") +def tor_browser_version_quoted(value): + if not value: + die("--with-tor-browser-version is required for Tor Browser.") + return '"{}"'.format(value[0]) + + +set_config("TOR_BROWSER_VERSION", tor_browser_version) +set_define("TOR_BROWSER_VERSION", tor_browser_version) +set_define("TOR_BROWSER_VERSION_QUOTED", tor_browser_version_quoted) + + +option( + "--enable-tor-browser-update", + help="Enable Tor Browser update" +) + + +@depends("--enable-tor-browser-update") +def tor_browser_update(value): + if value: + return True + + +set_config("TOR_BROWSER_UPDATE", tor_browser_update) +set_define("TOR_BROWSER_UPDATE", tor_browser_update) +add_old_configure_assignment("TOR_BROWSER_UPDATE", tor_browser_update) + + +option( + "--enable-tor-browser-data-outside-app-dir", + help="Enable Tor Browser data outside of app directory" +) + + +@depends("--enable-tor-browser-data-outside-app-dir") +def tor_browser_data_outside_app_dir(value): + if value: + return True + + +set_define( + "TOR_BROWSER_DATA_OUTSIDE_APP_DIR", tor_browser_data_outside_app_dir) +add_old_configure_assignment( + "TOR_BROWSER_DATA_OUTSIDE_APP_DIR", tor_browser_data_outside_app_dir) + + +option( + "--disable-tor-launcher", + help="Do not include Tor Launcher" +) + + +@depends("--disable-tor-launcher") +def tor_browser_disable_launcher(value): + if not value: + return True + + +set_config("TOR_BROWSER_DISABLE_TOR_LAUNCHER", tor_browser_disable_launcher) +set_define("TOR_BROWSER_DISABLE_TOR_LAUNCHER", tor_browser_disable_launcher) +add_old_configure_assignment( + "TOR_BROWSER_DISABLE_TOR_LAUNCHER", tor_browser_disable_launcher) + + # Please do not add configure checks from here on.
# Fallthrough to autoconf-based configure diff --git a/mozconfig-android-armv7 b/mozconfig-android-armv7 new file mode 100644 index 0000000000000..6ea51b0d5c052 --- /dev/null +++ b/mozconfig-android-armv7 @@ -0,0 +1,37 @@ +mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-arm-linux-androideabi +mk_add_options MOZ_APP_DISPLAYNAME="Tor Browser" +export MOZILLA_OFFICIAL=1 + +ac_add_options --enable-optimize +ac_add_options --enable-rust-simd +ac_add_options --enable-official-branding + +# Android +ac_add_options --enable-application=mobile/android +ac_add_options --target=arm-linux-androideabi +ac_add_options --with-android-ndk="$NDK_BASE" #Enter the android ndk location(ndk r17b) +ac_add_options --with-android-sdk="$SDK_BASE" #Enter the android sdk location +ac_add_options --with-branding=mobile/android/branding/alpha + +# Use Mozilla's Clang blobs +CC="$HOME/.mozbuild/clang/bin/clang" +CXX="$HOME/.mozbuild/clang/bin/clang++" + +#enable ccache to set amount of cache assigned for build. +ac_add_options --with-ccache + +ac_add_options --enable-strip +ac_add_options --disable-tests +ac_add_options --disable-debug +ac_add_options --disable-rust-debug + +ac_add_options --disable-updater +ac_add_options --disable-crashreporter +ac_add_options --disable-webrtc +ac_add_options --disable-parental-controls + +ac_add_options --enable-proxy-bypass-protection +ac_add_options --disable-system-policies + +# Disable telemetry +ac_add_options MOZ_TELEMETRY_REPORTING= diff --git a/mozconfig-linux-i686 b/mozconfig-linux-i686 index 16a75279e665a..4b03732c0e06f 100644 --- a/mozconfig-linux-i686 +++ b/mozconfig-linux-i686 @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
ac_add_options --target=i686-linux-gnu
diff --git a/mozconfig-linux-x86_64 b/mozconfig-linux-x86_64 index f14f1f4df86b3..06cf6e75b14fd 100644 --- a/mozconfig-linux-x86_64 +++ b/mozconfig-linux-x86_64 @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
ac_add_options --enable-default-toolkit=cairo-gtk3
diff --git a/mozconfig-linux-x86_64-asan b/mozconfig-linux-x86_64-asan index 62ca3feaad992..ec9c8c086eca4 100644 --- a/mozconfig-linux-x86_64-asan +++ b/mozconfig-linux-x86_64-asan @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
export CFLAGS="-fsanitize=address -Dxmalloc=myxmalloc" export CXXFLAGS="-fsanitize=address -Dxmalloc=myxmalloc" diff --git a/mozconfig-linux-x86_64-dev b/mozconfig-linux-x86_64-dev index 1e68b1c85103f..14488d870ddb0 100644 --- a/mozconfig-linux-x86_64-dev +++ b/mozconfig-linux-x86_64-dev @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
# This mozconfig file is not used in official builds. # It is only intended to be used when doing incremental Linux builds @@ -8,3 +8,7 @@ ac_add_options --enable-default-toolkit=cairo-gtk3
ac_add_options --disable-strip ac_add_options --disable-install-strip + +ac_add_options --disable-tor-launcher +ac_add_options --disable-tor-browser-update +ac_add_options --with-tor-browser-version=dev-build diff --git a/mozconfig-macos-x86_64 b/mozconfig-macos-x86_64 index 0fbda17fa63cd..e2286f972e627 100644 --- a/mozconfig-macos-x86_64 +++ b/mozconfig-macos-x86_64 @@ -1,3 +1,5 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
ac_add_options --enable-strip + +ac_add_options --enable-tor-browser-data-outside-app-dir diff --git a/mozconfig-windows-i686 b/mozconfig-windows-i686 index 1b9997d01e8e4..78ff05ecc8105 100644 --- a/mozconfig-windows-i686 +++ b/mozconfig-windows-i686 @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
ac_add_options --target=i686-w64-mingw32 ac_add_options --with-toolchain-prefix=i686-w64-mingw32- diff --git a/mozconfig-windows-x86_64 b/mozconfig-windows-x86_64 index e65ccce89db71..93b7a28fd361f 100644 --- a/mozconfig-windows-x86_64 +++ b/mozconfig-windows-x86_64 @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
ac_add_options --target=x86_64-w64-mingw32 ac_add_options --with-toolchain-prefix=x86_64-w64-mingw32- diff --git a/security/moz.build b/security/moz.build index 18e50f9dcc375..8d0427525487d 100644 --- a/security/moz.build +++ b/security/moz.build @@ -85,7 +85,7 @@ gyp_vars["nss_dist_obj_dir"] = "$PRODUCT_DIR/dist/bin" gyp_vars["disable_tests"] = 1 gyp_vars["disable_dbm"] = 1 gyp_vars["disable_libpkix"] = 1 -gyp_vars["enable_sslkeylogfile"] = 1 +gyp_vars["enable_sslkeylogfile"] = 0 # pkg-config won't reliably find zlib on our builders, so just force it. # System zlib is only used for modutil and signtool unless # SSL zlib is enabled, which we are disabling immediately below this. diff --git a/security/nss/lib/ssl/Makefile b/security/nss/lib/ssl/Makefile index 8a8b06f4b5086..90571bb3e2560 100644 --- a/security/nss/lib/ssl/Makefile +++ b/security/nss/lib/ssl/Makefile @@ -41,7 +41,7 @@ endif
# Enable key logging by default in debug builds, but not opt builds. # Logging still needs to be enabled at runtime through env vars. -NSS_ALLOW_SSLKEYLOGFILE ?= $(if $(BUILD_OPT),0,1) +NSS_ALLOW_SSLKEYLOGFILE ?= 0 ifeq (1,$(NSS_ALLOW_SSLKEYLOGFILE)) DEFINES += -DNSS_ALLOW_SSLKEYLOGFILE=1 endif diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm index 9b3acf6ecc302..ea10dc97535d2 100644 --- a/toolkit/modules/AppConstants.jsm +++ b/toolkit/modules/AppConstants.jsm @@ -354,6 +354,14 @@ this.AppConstants = Object.freeze({ MOZ_WIDGET_TOOLKIT: "@MOZ_WIDGET_TOOLKIT@", ANDROID_PACKAGE_NAME: "@ANDROID_PACKAGE_NAME@",
+ TOR_BROWSER_VERSION: "@TOR_BROWSER_VERSION@", + TOR_BROWSER_DATA_OUTSIDE_APP_DIR: +#ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR + true, +#else + false, +#endif + DEBUG_JS_MODULES: "@DEBUG_JS_MODULES@",
MOZ_BING_API_CLIENTID: "@MOZ_BING_API_CLIENTID@", @@ -431,4 +439,11 @@ this.AppConstants = Object.freeze({ #else false, #endif + + TOR_BROWSER_UPDATE: +#ifdef TOR_BROWSER_UPDATE + true, +#else + false, +#endif }); diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index 8ac56c81e6460..c6b2c421f4473 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -301,6 +301,9 @@ for var in ( if CONFIG[var]: DEFINES[var] = True
+if CONFIG["TOR_BROWSER_UPDATE"]: + DEFINES["TOR_BROWSER_UPDATE"] = 1 + JAR_MANIFESTS += ["jar.mn"]
DEFINES["TOPOBJDIR"] = TOPOBJDIR
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 4fd687e014228c714912c15843866435f8f5f70b Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Tue Jan 18 19:18:48 2022 +0100
Bug 40562: Added Tor-related preferences to 000-tor-browser.js
Before reordering patches, we used to keep the Tor-related patches (torbutton and tor-launcher) at the beginning. After that issue, we decided to move them towards the end, however we kept TB4: Tor Browser's Firefox preference overrides at the beginning because it influcences many other features. As a result, to keep bisect working, we split that commit, and moved all the preferences related to Tor (such as network.proxy.*) here. --- browser/app/profile/000-tor-browser.js | 93 +++++++++++++++++++++++++++ browser/installer/package-manifest.in | 2 +- browser/moz.build | 2 +- mobile/android/app/000-tor-browser-android.js | 47 ++++++++++++++ taskcluster/ci/source-test/mozlint.yml | 2 +- 5 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/browser/app/profile/000-tor-browser.js b/browser/app/profile/000-tor-browser.js new file mode 100644 index 0000000000000..4de0bdad97a0f --- /dev/null +++ b/browser/app/profile/000-tor-browser.js @@ -0,0 +1,93 @@ +#include 001-base-profile.js + +pref("app.update.notifyDuringDownload", true); +pref("app.update.url.manual", "https://www.torproject.org/download/languages/"); +pref("app.update.url.details", "https://www.torproject.org/download/"); +pref("app.update.badgeWaitTime", 0); +pref("app.releaseNotesURL", "about:blank"); +// disables the 'What's New?' link in the about dialog, otherwise we need to +// duplicate logic for generating the url to the blog post that is already more +// easily found in about:tor +pref("app.releaseNotesURL.aboutDialog", "about:blank"); +// point to our feedback url rather than Mozilla's +pref("app.feedback.baseURL", "https://support.torproject.org/%LOCALE%/get-in-touch/"); + +pref("browser.shell.checkDefaultBrowser", false); + +// Proxy and proxy security +pref("network.proxy.socks", "127.0.0.1"); +pref("network.proxy.socks_port", 9150); +pref("network.proxy.socks_remote_dns", true); +pref("network.proxy.no_proxies_on", ""); // For fingerprinting and local service vulns (#10419) +pref("network.proxy.allow_hijacking_localhost", true); // Allow proxies for localhost (#31065) +pref("network.proxy.type", 1); +// Bug 40548: Disable proxy-bypass +pref("network.proxy.failover_direct", false); +pref("network.security.ports.banned", "9050,9051,9150,9151"); +pref("network.dns.disabled", true); // This should cover the #5741 patch for DNS leaks +pref("network.http.max-persistent-connections-per-proxy", 256); + +pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-list":[],"PersonalToolbar":["personal-bookmarks"],"nav-bar":["back-button","forward-button","stop-reload-button","urlbar-container","torbutton-button","security-level-button","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","new- [...] + +// Treat .onions as secure +pref("dom.securecontext.whitelist_onions", true); + +#expand pref("torbrowser.version", __TOR_BROWSER_VERSION_QUOTED__); + +// Old torbutton prefs + +// debug prefs +pref("extensions.torbutton.loglevel",4); +pref("extensions.torbutton.logmethod",1); // 0=stdout, 1=errorconsole, 2=debuglog + +// Display prefs +pref("extensions.torbutton.display_circuit", true); +pref("extensions.torbutton@torproject.org.description", "chrome://torbutton/locale/torbutton.properties"); +pref("extensions.torbutton.updateNeeded", false); + +// Tor check and proxy prefs +pref("extensions.torbutton.test_enabled",true); +pref("extensions.torbutton.test_url","https://check.torproject.org/?TorButton=true"); +pref("extensions.torbutton.local_tor_check",true); +pref("extensions.torbutton.versioncheck_url","https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions"); +pref("extensions.torbutton.versioncheck_enabled",true); +pref("extensions.torbutton.use_nontor_proxy",false); + +// State prefs: +pref("extensions.torbutton.startup",false); +pref("extensions.torbutton.inserted_button",false); +pref("extensions.torbutton.inserted_security_level",false); + +// This is only used when letterboxing is disabled. +// See #7255 for details. We display the warning three times to make sure the +// user did not click on it by accident. +pref("extensions.torbutton.maximize_warnings_remaining", 3); + +// Security prefs: +pref("extensions.torbutton.clear_http_auth",true); +pref("extensions.torbutton.close_newnym",true); +pref("extensions.torbutton.resize_new_windows",false); +pref("extensions.torbutton.startup_state", 2); // 0=non-tor, 1=tor, 2=last +pref("extensions.torbutton.tor_memory_jar",false); +pref("extensions.torbutton.nontor_memory_jar",false); +pref("extensions.torbutton.launch_warning",true); + +// Opt out of Firefox addon pings: +// https://developer.mozilla.org/en/Addons/Working_with_AMO +pref("extensions.torbutton@torproject.org.getAddons.cache.enabled", false); + +// Security Slider +pref("extensions.torbutton.security_slider", 4); +pref("extensions.torbutton.security_custom", false); + +pref("extensions.torbutton.confirm_plugins", true); +pref("extensions.torbutton.confirm_newnym", true); + +pref("extensions.torbutton.noscript_inited", false); +pref("extensions.torbutton.noscript_persist", false); + +// Browser home page: +pref("browser.startup.homepage", "about:tor"); + +// This pref specifies an ad-hoc "version" for various pref update hacks we need to do +pref("extensions.torbutton.pref_fixup_version", 0); diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 539cd6419e673..8f52da54f7b96 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -283,7 +283,7 @@ @RESPATH@/browser/defaults/settings/blocklists @RESPATH@/browser/defaults/settings/main @RESPATH@/browser/defaults/settings/security-state -@RESPATH@/browser/@PREF_DIR@/001-base-profile.js +@RESPATH@/browser/@PREF_DIR@/000-tor-browser.js
; Warning: changing the path to channel-prefs.js can cause bugs (Bug 756325) ; Technically this is an app pref file, but we are keeping it in the original diff --git a/browser/moz.build b/browser/moz.build index 0df0a824f9ad4..d72932988fac6 100644 --- a/browser/moz.build +++ b/browser/moz.build @@ -56,7 +56,7 @@ if CONFIG["MOZ_UPDATE_AGENT"]: # These files are specified in this moz.build to pick up DIST_SUBDIR as set in # this directory, which is un-set in browser/app. JS_PREFERENCE_PP_FILES += [ - "app/profile/001-base-profile.js", + "app/profile/000-tor-browser.js", "app/profile/firefox.js", ] FINAL_TARGET_FILES.defaults += ["app/permissions"] diff --git a/mobile/android/app/000-tor-browser-android.js b/mobile/android/app/000-tor-browser-android.js new file mode 100644 index 0000000000000..61c8a0cd7fa18 --- /dev/null +++ b/mobile/android/app/000-tor-browser-android.js @@ -0,0 +1,47 @@ +// Import all prefs from the canonical file +// We override mobile-specific prefs below +// Tor Browser for Android +// Do not edit this file. + +#include ../../../browser/app/profile/000-tor-browser.js + +// Space separated list of URLs that are allowed to send objects (instead of +// only strings) through webchannels. This list is duplicated in browser/app/profile/firefox.js +pref("webchannel.allowObject.urlWhitelist", ""); + +// Disable browser auto updaters +pref("app.update.auto", false); +pref("browser.startup.homepage_override.mstone", "ignore"); + +// Clear data on quit +pref("privacy.clearOnShutdown.cache", true); +pref("privacy.clearOnShutdown.cookies",true); +pref("privacy.clearOnShutdown.downloads",true); +pref("privacy.clearOnShutdown.formdata",true); +pref("privacy.clearOnShutdown.history",true); +pref("privacy.clearOnShutdown.offlineApps",true); +pref("privacy.clearOnShutdown.passwords",true); +pref("privacy.clearOnShutdown.sessions",true); +pref("privacy.clearOnShutdown.siteSettings",true); + +// controls if we want camera support +pref("media.realtime_decoder.enabled", false); + +// Enable touch events on Android (highlighting text, etc) +pref("dom.w3c_touch_events.enabled", 2); + +// Ensure that pointer events are disabled +pref("dom.w3c_pointer_events.multiprocess.android.enabled", false); + +// No HLS support for now due to browser freezing, see: #29859. +pref("media.hls.enabled", false); + +// Inherit locale from the OS, used for multi-locale builds +pref("intl.locale.requested", ""); + +// Disable WebAuthn. It requires Google Play Services, so it isn't +// available, but avoid any potential problems. +pref("security.webauth.webauthn_enable_android_fido2", false); + +// Disable the External App Blocker on Android +pref("extensions.torbutton.launch_warning", false); diff --git a/taskcluster/ci/source-test/mozlint.yml b/taskcluster/ci/source-test/mozlint.yml index 589ad7a0af1e4..f4e3d18a710fe 100644 --- a/taskcluster/ci/source-test/mozlint.yml +++ b/taskcluster/ci/source-test/mozlint.yml @@ -163,7 +163,7 @@ lintpref: files-changed: - 'modules/libpref/init/all.js' - 'modules/libpref/init/StaticPrefList.yaml' - - 'browser/app/profile/001-base-profile.js' + - 'browser/app/profile/000-tor-browser.js' - 'browser/app/profile/firefox.js' - 'mobile/android/app/mobile.js' - 'devtools/client/preferences/debugger.js'
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 690774f96eee3fe1f71cf620761d6b8877161428 Author: fxbrit fxbrit@tuta.io AuthorDate: Wed May 11 01:58:51 2022 +0200
squash! Bug 40562: Added Tor-related preferences to 000-tor-browser.js
Bug 40743: add dom.securecontext.allowlist_onions --- browser/app/profile/000-tor-browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/browser/app/profile/000-tor-browser.js b/browser/app/profile/000-tor-browser.js index 4de0bdad97a0f..effd852c546e0 100644 --- a/browser/app/profile/000-tor-browser.js +++ b/browser/app/profile/000-tor-browser.js @@ -31,6 +31,7 @@ pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-
// Treat .onions as secure pref("dom.securecontext.whitelist_onions", true); +pref("dom.securecontext.allowlist_onions", true); // FF97+ 1744006 renamed from whitelist
#expand pref("torbrowser.version", __TOR_BROWSER_VERSION_QUOTED__);
@@ -49,7 +50,6 @@ pref("extensions.torbutton.updateNeeded", false); pref("extensions.torbutton.test_enabled",true); pref("extensions.torbutton.test_url","https://check.torproject.org/?TorButton=true"); pref("extensions.torbutton.local_tor_check",true); -pref("extensions.torbutton.versioncheck_url","https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions"); pref("extensions.torbutton.versioncheck_enabled",true); pref("extensions.torbutton.use_nontor_proxy",false);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit ff80e1436cfe44742393a2725c4f272c0011b217 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Fri Mar 18 14:20:02 2016 -0400
Bug 13252: Do not store data in the app bundle
When --enable-tor-browser-data-outside-app-dir is enabled, all user data is stored in a directory named TorBrowser-Data which is located next to the application directory.
Display an informative error message if the TorBrowser-Data directory cannot be created due to an "access denied" or a "read only volume" error.
On Mac OS, add support for the --invisible command line option which is used by the meek-http-helper to avoid showing an icon for the helper browser on the dock. --- toolkit/xre/nsAppRunner.cpp | 56 +++++++++++++++++--- toolkit/xre/nsXREDirProvider.cpp | 23 +++++++- toolkit/xre/nsXREDirProvider.h | 6 +++ xpcom/io/TorFileUtils.cpp | 96 ++++++++++++++++++++++++++++++++++ xpcom/io/TorFileUtils.h | 32 ++++++++++++ xpcom/io/moz.build | 5 ++ xpcom/io/nsAppFileLocationProvider.cpp | 13 ++++- 7 files changed, 223 insertions(+), 8 deletions(-)
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index acc92392cf239..90bf00811b646 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2587,6 +2587,21 @@ static ReturnAbortOnError ShowProfileManager( return LaunchChild(false, true); }
+#ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR +static ProfileStatus CheckTorBrowserDataWriteAccess(nsIFile* aAppDir) { + // Check whether we can write to the directory that will contain + // TorBrowser-Data. + nsCOMPtr<nsIFile> tbDataDir; + nsresult rv = + nsXREDirProvider::GetTorBrowserUserDataDir(getter_AddRefs(tbDataDir)); + NS_ENSURE_SUCCESS(rv, PROFILE_STATUS_OTHER_ERROR); + nsCOMPtr<nsIFile> tbDataDirParent; + rv = tbDataDir->GetParent(getter_AddRefs(tbDataDirParent)); + NS_ENSURE_SUCCESS(rv, PROFILE_STATUS_OTHER_ERROR); + return nsToolkitProfileService::CheckProfileWriteAccess(tbDataDirParent); +} +#endif + static bool gDoMigration = false; static bool gDoProfileReset = false; static nsCOMPtr<nsIToolkitProfile> gResetOldProfile; @@ -3616,6 +3631,14 @@ int XREMain::XRE_mainInit(bool* aExitFlag) { if (PR_GetEnv("XRE_MAIN_BREAK")) NS_BREAK(); #endif
+#if defined(XP_MACOSX) && defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + bool hideDockIcon = (CheckArg("invisible") == ARG_FOUND); + if (hideDockIcon) { + ProcessSerialNumber psn = {0, kCurrentProcess}; + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); + } +#endif + IncreaseDescriptorLimits();
#ifdef USE_GLX_TEST @@ -4462,7 +4485,34 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) { } #endif
+#if (defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)) || \ + defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + nsCOMPtr<nsIFile> exeFile, exeDir; + bool persistent; + rv = mDirProvider.GetFile(XRE_EXECUTABLE_FILE, &persistent, + getter_AddRefs(exeFile)); + NS_ENSURE_SUCCESS(rv, 1); + rv = exeFile->GetParent(getter_AddRefs(exeDir)); + NS_ENSURE_SUCCESS(rv, 1); +#endif + rv = NS_NewToolkitProfileService(getter_AddRefs(mProfileSvc)); +#ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR + if (NS_FAILED(rv)) { + // NS_NewToolkitProfileService() returns a generic NS_ERROR_FAILURE error + // if creation of the TorBrowser-Data directory fails due to access denied + // or because of a read-only disk volume. Do an extra check here to detect + // these errors so we can display an informative error message. + ProfileStatus status = CheckTorBrowserDataWriteAccess(exeDir); + if ((PROFILE_STATUS_ACCESS_DENIED == status) || + (PROFILE_STATUS_READ_ONLY == status)) { + ProfileErrorDialog(nullptr, nullptr, status, nullptr, mNativeApp, + nullptr); + return 1; + } + } +#endif + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { PR_fprintf(PR_STDERR, "Error: Access was denied while trying to open files in " @@ -4568,12 +4618,6 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) { if (CheckArg("test-process-updates")) { SaveToEnv("MOZ_TEST_PROCESS_UPDATES=1"); } - nsCOMPtr<nsIFile> exeFile, exeDir; - rv = mDirProvider.GetFile(XRE_EXECUTABLE_FILE, &persistent, - getter_AddRefs(exeFile)); - NS_ENSURE_SUCCESS(rv, 1); - rv = exeFile->GetParent(getter_AddRefs(exeDir)); - NS_ENSURE_SUCCESS(rv, 1); ProcessUpdates(mDirProvider.GetGREDir(), exeDir, updRoot, gRestartArgc, gRestartArgv, mAppData->version); if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp index 4360b23c820fe..85f84278d886b 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -56,6 +56,8 @@ # include "nsIPK11Token.h" #endif
+#include "TorFileUtils.h" + #include <stdlib.h>
#ifdef XP_WIN @@ -1375,7 +1377,13 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, return gDataDirHome->Clone(aFile); }
-#if defined(RELATIVE_PROFILE_DIRECTORY) +#if defined(RELATIVE_PROFILE_DIRECTORY) || \ + defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR + rv = GetTorBrowserUserDataDir(getter_AddRefs(localDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = localDir->AppendNative("Browser"_ns); +# else RefPtr<nsXREDirProvider> singleton = GetSingleton(); if (!singleton) { return NS_ERROR_OUT_OF_MEMORY; @@ -1385,6 +1393,7 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, nsAutoCString profileDir(RELATIVE_PROFILE_DIRECTORY); rv = localDir->SetRelativePath(localDir.get(), profileDir); NS_ENSURE_SUCCESS(rv, rv); +#endif if (aLocal) { rv = localDir->AppendNative("Caches"_ns); NS_ENSURE_SUCCESS(rv, rv); @@ -1563,6 +1572,18 @@ nsresult nsXREDirProvider::GetUserDataDirectory(nsIFile** aFile, bool aLocal) { return NS_OK; }
+nsresult nsXREDirProvider::GetTorBrowserUserDataDir(nsIFile** aFile) { + NS_ENSURE_ARG_POINTER(aFile); + nsCOMPtr<nsIFile> appRootDir; + RefPtr<nsXREDirProvider> singleton = GetSingleton(); + if (!singleton) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsresult rv = singleton->GetAppRootDir(getter_AddRefs(appRootDir)); + NS_ENSURE_SUCCESS(rv, rv); + return TorBrowser_GetUserDataDir(appRootDir, aFile); +} + nsresult nsXREDirProvider::GetAppRootDir(nsIFile** aFile) { bool persistent = false; nsCOMPtr<nsIFile> file, appRootDir; diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h index a76b087d785a3..2e6e3339ed15a 100644 --- a/toolkit/xre/nsXREDirProvider.h +++ b/toolkit/xre/nsXREDirProvider.h @@ -109,6 +109,12 @@ class nsXREDirProvider final : public nsIDirectoryServiceProvider2, */ nsresult GetProfileDir(nsIFile** aResult);
+ /** + * Get the TorBrowser user data directory by calling the + * TorBrowser_GetUserDataDir() utility function. + */ + static nsresult GetTorBrowserUserDataDir(nsIFile** aFile); + /** * Get the path to the base application directory. * diff --git a/xpcom/io/TorFileUtils.cpp b/xpcom/io/TorFileUtils.cpp new file mode 100644 index 0000000000000..30a78f18123e5 --- /dev/null +++ b/xpcom/io/TorFileUtils.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TorFileUtils.h" +#include "nsString.h" +#ifdef MOZ_WIDGET_COCOA +# include <Carbon/Carbon.h> +# include "nsILocalFileMac.h" +#endif + +nsresult TorBrowser_GetUserDataDir(nsIFile* aAppDir, nsIFile** aFile) { + NS_ENSURE_ARG_POINTER(aFile); + nsCOMPtr<nsIFile> tbDataDir; + +#ifdef ANDROID + const char* homeDir = getenv("HOME"); + if (!homeDir || !*homeDir) return NS_ERROR_FAILURE; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true, + getter_AddRefs(tbDataDir)); + NS_ENSURE_SUCCESS(rv, rv); +#elif defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + nsAutoCString tbDataLeafName("TorBrowser-Data"_ns); +# ifndef XP_MACOSX + // On all platforms except Mac OS, we always operate in a "portable" mode + // where the TorBrowser-Data directory is located next to the application. + nsresult rv = aAppDir->GetParent(getter_AddRefs(tbDataDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = tbDataDir->AppendNative(tbDataLeafName); + NS_ENSURE_SUCCESS(rv, rv); +# else + // For Mac OS, determine whether we should store user data in the OS's + // standard location (i.e., under ~/Library/Application Support). We use + // the OS location if (1) the application is installed in a directory whose + // path contains "/Applications" or (2) the TorBrowser-Data directory does + // not exist and cannot be created (which probably means we lack write + // permission to the directory that contains the application). + nsAutoString appRootPath; + nsresult rv = aAppDir->GetPath(appRootPath); + NS_ENSURE_SUCCESS(rv, rv); + bool useOSLocation = + (appRootPath.Find("/Applications", true /* ignore case */) >= 0); + if (!useOSLocation) { + // We hope to use the portable (aka side-by-side) approach, but before we + // commit to that, let's ensure that we can create the TorBrowser-Data + // directory. If it already exists, we will try to use it; if not and we + // fail to create it, we will switch to ~/Library/Application Support. + rv = aAppDir->GetParent(getter_AddRefs(tbDataDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = tbDataDir->AppendNative(tbDataLeafName); + NS_ENSURE_SUCCESS(rv, rv); + bool exists = false; + rv = tbDataDir->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = tbDataDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + useOSLocation = NS_FAILED(rv); + } + + if (useOSLocation) { + // We are using ~/Library/Application Support/TorBrowser-Data. We do not + // need to create that directory here because the code in nsXREDirProvider + // will do so (and the user should always have write permission for + // ~/Library/Application Support; if they do not we have no more options). + FSRef fsRef; + OSErr err = ::FSFindFolder(kUserDomain, kApplicationSupportFolderType, + kCreateFolder, &fsRef); + NS_ENSURE_FALSE(err, NS_ERROR_FAILURE); + // To convert the FSRef returned by FSFindFolder() into an nsIFile that + // points to ~/Library/Application Support, we first create an empty + // nsIFile object (no path) and then use InitWithFSRef() to set the + // path. + rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(tbDataDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsILocalFileMac> dirFileMac = do_QueryInterface(tbDataDir); + if (!dirFileMac) return NS_ERROR_UNEXPECTED; + rv = dirFileMac->InitWithFSRef(&fsRef); + NS_ENSURE_SUCCESS(rv, rv); + rv = tbDataDir->AppendNative(tbDataLeafName); + NS_ENSURE_SUCCESS(rv, rv); + } +# endif + +#else + // User data is embedded within the application directory (i.e., + // TOR_BROWSER_DATA_OUTSIDE_APP_DIR is not defined). + nsresult rv = aAppDir->Clone(getter_AddRefs(tbDataDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = tbDataDir->AppendNative("TorBrowser"_ns); + NS_ENSURE_SUCCESS(rv, rv); +#endif + + tbDataDir.forget(aFile); + return NS_OK; +} diff --git a/xpcom/io/TorFileUtils.h b/xpcom/io/TorFileUtils.h new file mode 100644 index 0000000000000..22cae5a660f46 --- /dev/null +++ b/xpcom/io/TorFileUtils.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TorFileUtils_h__ +#define TorFileUtils_h__ + +#include "nsIFile.h" + +/** + * TorBrowser_GetUserDataDir + * + * Retrieve the Tor Browser user data directory. + * When built with --enable-tor-browser-data-outside-app-dir, the directory + * is next to the application directory, except on Mac OS where it may be + * there or it may be at ~/Library/Application Support/TorBrowser-Data (the + * latter location is used if the .app bundle is in a directory whose path + * contains /Applications or if we lack write access to the directory that + * contains the .app). + * When built without --enable-tor-browser-data-outside-app-dir, this + * directory is TorBrowser.app/TorBrowser. + * + * @param aAppDir The path to Tor Browser directory + * @param aFile Out parameter that is set to the Tor Browser user data + * directory. + * @return NS_OK on success. Error otherwise. + */ +extern nsresult TorBrowser_GetUserDataDir(nsIFile* aAppDir, nsIFile** aFile); + +#endif // !TorFileUtils_h__ diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build index d28c426e7bd73..af7b5be04f6ee 100644 --- a/xpcom/io/moz.build +++ b/xpcom/io/moz.build @@ -86,6 +86,7 @@ EXPORTS += [ "nsUnicharInputStream.h", "nsWildCard.h", "SpecialSystemDirectory.h", + "TorFileUtils.h", ]
EXPORTS.mozilla += [ @@ -137,6 +138,10 @@ UNIFIED_SOURCES += [ "SpecialSystemDirectory.cpp", ]
+SOURCES += [ + "TorFileUtils.cpp", +] + if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": SOURCES += [ "CocoaFileUtils.mm", diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp index 60b1775eda971..85ce71c5016c4 100644 --- a/xpcom/io/nsAppFileLocationProvider.cpp +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -27,6 +27,8 @@ # include <sys/param.h> #endif
+#include "TorFileUtils.h" + // WARNING: These hard coded names need to go away. They need to // come from localizable resources
@@ -247,7 +249,8 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, bool exists; nsCOMPtr<nsIFile> localDir;
-#if defined(RELATIVE_PROFILE_DIRECTORY) +#if defined(RELATIVE_PROFILE_DIRECTORY) || \ + defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) nsCOMPtr<nsIProperties> directoryService( do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); @@ -269,10 +272,18 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, file = appRootDir; }
+#ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR + rv = TorBrowser_GetUserDataDir(appRootDir, getter_AddRefs(localDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = localDir->AppendNative("Browser"_ns); +#else localDir = appRootDir; nsAutoCString profileDir(RELATIVE_PROFILE_DIRECTORY); rv = localDir->SetRelativePath(localDir.get(), profileDir); NS_ENSURE_SUCCESS(rv, rv); +#endif + + NS_ENSURE_SUCCESS(rv, rv);
if (aLocal) { rv = localDir->AppendNative("Caches"_ns);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit d4fbdbc386c5378e608af171a1b3c6230d3aeed8 Author: Richard Pospesel richard@torproject.org AuthorDate: Fri Aug 6 16:39:03 2021 +0200
Bug 40597: Implement TorSettings module
- migrated in-page settings read/write implementation from about:preferences#tor to the TorSettings module - TorSettings initially loads settings from the tor daemon, and saves them to firefox prefs - TorSettings notifies observers when a setting has changed; currently only QuickStart notification is implemented for parity with previous preference notify logic in about:torconnect and about:preferences#tor - about:preferences#tor, and about:torconnect now read and write settings thorugh the TorSettings module - all tor settings live in the torbrowser.settings.* preference branch - removed unused pref modify permission for about:torconnect content page from AsyncPrefs.jsm
Bug 40645: Migrate Moat APIs to Moat.jsm module --- browser/modules/BridgeDB.jsm | 61 ++ browser/modules/Moat.jsm | 780 +++++++++++++++++++++ browser/modules/TorConnect.jsm | 755 ++++++++++++++++++++ browser/modules/TorProtocolService.jsm | 502 +++++++++++++ browser/modules/TorSettings.jsm | 674 ++++++++++++++++++ browser/modules/moz.build | 4 + .../processsingleton/MainProcessSingleton.jsm | 5 + 7 files changed, 2781 insertions(+)
diff --git a/browser/modules/BridgeDB.jsm b/browser/modules/BridgeDB.jsm new file mode 100644 index 0000000000000..50665710ebf4c --- /dev/null +++ b/browser/modules/BridgeDB.jsm @@ -0,0 +1,61 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["BridgeDB"]; + +const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm"); + +var BridgeDB = { + _moatRPC: null, + _challenge: null, + _image: null, + _bridges: null, + + get currentCaptchaImage() { + return this._image; + }, + + get currentBridges() { + return this._bridges; + }, + + async submitCaptchaGuess(solution) { + if (!this._moatRPC) { + this._moatRPC = new MoatRPC(); + await this._moatRPC.init(); + } + + const response = await this._moatRPC.check( + "obfs4", + this._challenge, + solution, + false + ); + this._bridges = response?.bridges; + return this._bridges; + }, + + async requestNewCaptchaImage() { + try { + if (!this._moatRPC) { + this._moatRPC = new MoatRPC(); + await this._moatRPC.init(); + } + + const response = await this._moatRPC.fetch(["obfs4"]); + this._challenge = response.challenge; + this._image = + "data:image/jpeg;base64," + encodeURIComponent(response.image); + } catch (err) { + console.log(`error : ${err}`); + } + return this._image; + }, + + close() { + this._moatRPC?.uninit(); + this._moatRPC = null; + this._challenge = null; + this._image = null; + this._bridges = null; + }, +}; diff --git a/browser/modules/Moat.jsm b/browser/modules/Moat.jsm new file mode 100644 index 0000000000000..2995a9148f0a3 --- /dev/null +++ b/browser/modules/Moat.jsm @@ -0,0 +1,780 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["MoatRPC"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const { Subprocess } = ChromeUtils.import( + "resource://gre/modules/Subprocess.jsm" +); + +const { TorLauncherUtil } = ChromeUtils.import( + "resource://torlauncher/modules/tl-util.jsm" +); + +const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +const { TorSettings, TorBridgeSource } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +const TorLauncherPrefs = Object.freeze({ + bridgedb_front: "extensions.torlauncher.bridgedb_front", + bridgedb_reflector: "extensions.torlauncher.bridgedb_reflector", + moat_service: "extensions.torlauncher.moat_service", +}); + +// Config keys used to query tor daemon properties +const TorConfigKeys = Object.freeze({ + clientTransportPlugin: "ClientTransportPlugin", +}); + +// +// Launches and controls the PT process lifetime +// +class MeekTransport { + constructor() { + this._inited = false; + this._meekClientProcess = null; + this._meekProxyType = null; + this._meekProxyAddress = null; + this._meekProxyPort = 0; + this._meekProxyUsername = null; + this._meekProxyPassword = null; + } + + // launches the meekprocess + async init() { + // ensure we haven't already init'd + if (this._inited) { + throw new Error("MeekTransport: Already initialized"); + } + + // cleanup function for killing orphaned pt process + let onException = () => {}; + try { + // figure out which pluggable transport to use + const supportedTransports = ["meek", "meek_lite"]; + let transportPlugins = await TorProtocolService.readStringArraySetting( + TorConfigKeys.clientTransportPlugin + ); + + let { meekTransport, meekClientPath, meekClientArgs } = (() => { + for (const line of transportPlugins) { + let tokens = line.split(" "); + if (tokens.length > 2 && tokens[1] == "exec") { + let transportArray = tokens[0].split(",").map(aStr => aStr.trim()); + let transport = transportArray.find(aTransport => + supportedTransports.includes(aTransport) + ); + + if (transport != undefined) { + return { + meekTransport: transport, + meekClientPath: tokens[2], + meekClientArgs: tokens.slice(3), + }; + } + } + } + + return { + meekTransport: null, + meekClientPath: null, + meekClientArgs: null, + }; + })(); + + // Convert meek client path to absolute path if necessary + let meekWorkDir = await TorLauncherUtil.getTorFile( + "pt-startup-dir", + false + ); + let re = TorLauncherUtil.isWindows ? /^[A-Za-z]:\/ : /^//; + if (!re.test(meekClientPath)) { + let meekPath = meekWorkDir.clone(); + meekPath.appendRelativePath(meekClientPath); + meekClientPath = meekPath.path; + } + + // Construct the per-connection arguments. + let meekClientEscapedArgs = ""; + const meekReflector = Services.prefs.getStringPref( + TorLauncherPrefs.bridgedb_reflector + ); + + // Escape aValue per section 3.5 of the PT specification: + // First the "<Key>=<Value>" formatted arguments MUST be escaped, + // such that all backslash, equal sign, and semicolon characters + // are escaped with a backslash. + let escapeArgValue = aValue => { + if (!aValue) { + return ""; + } + + let rv = aValue.replace(/\/g, "\\"); + rv = rv.replace(/=/g, "\="); + rv = rv.replace(/;/g, "\;"); + return rv; + }; + + if (meekReflector) { + meekClientEscapedArgs += "url="; + meekClientEscapedArgs += escapeArgValue(meekReflector); + } + const meekFront = Services.prefs.getStringPref( + TorLauncherPrefs.bridgedb_front + ); + if (meekFront) { + if (meekClientEscapedArgs.length) { + meekClientEscapedArgs += ";"; + } + meekClientEscapedArgs += "front="; + meekClientEscapedArgs += escapeArgValue(meekFront); + } + + // Setup env and start meek process + let ptStateDir = TorLauncherUtil.getTorFile("tordatadir", false); + let meekHelperProfileDir = TorLauncherUtil.getTorFile( + "pt-profiles-dir", + true + ); + ptStateDir.append("pt_state"); // Match what tor uses. + meekHelperProfileDir.appendRelativePath("profile.moat-http-helper"); + + let envAdditions = { + TOR_PT_MANAGED_TRANSPORT_VER: "1", + TOR_PT_STATE_LOCATION: ptStateDir.path, + TOR_PT_EXIT_ON_STDIN_CLOSE: "1", + TOR_PT_CLIENT_TRANSPORTS: meekTransport, + TOR_BROWSER_MEEK_PROFILE: meekHelperProfileDir.path, + }; + if (TorSettings.proxy.enabled) { + envAdditions.TOR_PT_PROXY = TorSettings.proxy.uri; + } + + let opts = { + command: meekClientPath, + arguments: meekClientArgs, + workdir: meekWorkDir.path, + environmentAppend: true, + environment: envAdditions, + stderr: "pipe", + }; + + // Launch meek client + let meekClientProcess = await Subprocess.call(opts); + // kill our process if exception is thrown + onException = () => { + meekClientProcess.kill(); + }; + + // Callback chain for reading stderr + let stderrLogger = async () => { + if (this._meekClientProcess) { + let errString = await this._meekClientProcess.stderr.readString(); + console.log(`MeekTransport: stderr => ${errString}`); + await stderrLogger(); + } + }; + stderrLogger(); + + // Read pt's stdout until terminal (CMETHODS DONE) is reached + // returns array of lines for parsing + let getInitLines = async (stdout = "") => { + let string = await meekClientProcess.stdout.readString(); + stdout += string; + + // look for the final message + const CMETHODS_DONE = "CMETHODS DONE"; + let endIndex = stdout.lastIndexOf(CMETHODS_DONE); + if (endIndex != -1) { + endIndex += CMETHODS_DONE.length; + return stdout.substr(0, endIndex).split("\n"); + } + return getInitLines(stdout); + }; + + // read our lines from pt's stdout + let meekInitLines = await getInitLines(); + // tokenize our pt lines + let meekInitTokens = meekInitLines.map(line => { + let tokens = line.split(" "); + return { + keyword: tokens[0], + args: tokens.slice(1), + }; + }); + + let meekProxyType = null; + let meekProxyAddr = null; + let meekProxyPort = 0; + + // parse our pt tokens + for (const { keyword, args } of meekInitTokens) { + const argsJoined = args.join(" "); + let keywordError = false; + switch (keyword) { + case "VERSION": { + if (args.length != 1 || args[0] !== "1") { + keywordError = true; + } + break; + } + case "PROXY": { + if (args.length != 1 || args[0] !== "DONE") { + keywordError = true; + } + break; + } + case "CMETHOD": { + if (args.length != 3) { + keywordError = true; + break; + } + const transport = args[0]; + const proxyType = args[1]; + const addrPortString = args[2]; + const addrPort = addrPortString.split(":"); + + if (transport !== meekTransport) { + throw new Error( + `MeekTransport: Expected ${meekTransport} but found ${transport}` + ); + } + if (!["socks4", "socks4a", "socks5"].includes(proxyType)) { + throw new Error( + `MeekTransport: Invalid proxy type => ${proxyType}` + ); + } + if (addrPort.length != 2) { + throw new Error( + `MeekTransport: Invalid proxy address => ${addrPortString}` + ); + } + const addr = addrPort[0]; + const port = parseInt(addrPort[1]); + if (port < 1 || port > 65535) { + throw new Error(`MeekTransport: Invalid proxy port => ${port}`); + } + + // convert proxy type to strings used by protocol-proxy-servce + meekProxyType = proxyType === "socks5" ? "socks" : "socks4"; + meekProxyAddr = addr; + meekProxyPort = port; + + break; + } + // terminal + case "CMETHODS": { + if (args.length != 1 || args[0] !== "DONE") { + keywordError = true; + } + break; + } + // errors (all fall through): + case "VERSION-ERROR": + case "ENV-ERROR": + case "PROXY-ERROR": + case "CMETHOD-ERROR": + throw new Error(`MeekTransport: ${keyword} => '${argsJoined}'`); + } + if (keywordError) { + throw new Error( + `MeekTransport: Invalid ${keyword} keyword args => '${argsJoined}'` + ); + } + } + + this._meekClientProcess = meekClientProcess; + // register callback to cleanup on process exit + this._meekClientProcess.wait().then(exitObj => { + this._meekClientProcess = null; + this.uninit(); + }); + + this._meekProxyType = meekProxyType; + this._meekProxyAddress = meekProxyAddr; + this._meekProxyPort = meekProxyPort; + + // socks5 + if (meekProxyType === "socks") { + if (meekClientEscapedArgs.length <= 255) { + this._meekProxyUsername = meekClientEscapedArgs; + this._meekProxyPassword = "\x00"; + } else { + this._meekProxyUsername = meekClientEscapedArgs.substring(0, 255); + this._meekProxyPassword = meekClientEscapedArgs.substring(255); + } + // socks4 + } else { + this._meekProxyUsername = meekClientEscapedArgs; + this._meekProxyPassword = undefined; + } + + this._inited = true; + } catch (ex) { + onException(); + throw ex; + } + } + + async uninit() { + this._inited = false; + + await this._meekClientProcess?.kill(); + this._meekClientProcess = null; + this._meekProxyType = null; + this._meekProxyAddress = null; + this._meekProxyPort = 0; + this._meekProxyUsername = null; + this._meekProxyPassword = null; + } +} + +// +// Callback object with a cached promise for the returned Moat data +// +class MoatResponseListener { + constructor() { + this._response = ""; + // we need this promise here because await nsIHttpChannel::asyncOpen does + // not return only once the request is complete, it seems to return + // after it begins, so we have to get the result from this listener object. + // This promise is only resolved once onStopRequest is called + this._responsePromise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + + // callers wait on this for final response + response() { + return this._responsePromise; + } + + // noop + onStartRequest(request) {} + + // resolve or reject our Promise + onStopRequest(request, status) { + try { + if (!Components.isSuccessCode(status)) { + const errorMessage = TorLauncherUtil.getLocalizedStringForError(status); + this._reject(new Error(errorMessage)); + } + if (request.responseStatus != 200) { + this._reject(new Error(request.responseStatusText)); + } + } catch (err) { + this._reject(err); + } + this._resolve(this._response); + } + + // read response data + onDataAvailable(request, stream, offset, length) { + const scriptableStream = Cc[ + "@mozilla.org/scriptableinputstream;1" + ].createInstance(Ci.nsIScriptableInputStream); + scriptableStream.init(stream); + this._response += scriptableStream.read(length); + } +} + +class InternetTestResponseListener { + constructor() { + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + + // callers wait on this for final response + status() { + return this._promise; + } + + onStartRequest(request) {} + + // resolve or reject our Promise + onStopRequest(request, status) { + let statuses = {}; + try { + statuses = { + components: status, + successful: Components.isSuccessCode(status), + http: request.responseStatus, + }; + } catch (err) { + this._reject(err); + } + this._resolve(statuses); + } + + onDataAvailable(request, stream, offset, length) { + // We do not care of the actual data, as long as we have a successful + // connection + } +} + +// constructs the json objects and sends the request over moat +class MoatRPC { + constructor() { + this._meekTransport = null; + this._inited = false; + } + + get inited() { + return this._inited; + } + + async init() { + if (this._inited) { + throw new Error("MoatRPC: Already initialized"); + } + + let meekTransport = new MeekTransport(); + await meekTransport.init(); + this._meekTransport = meekTransport; + this._inited = true; + } + + async uninit() { + await this._meekTransport?.uninit(); + this._meekTransport = null; + this._inited = false; + } + + _makeHttpHandler(uriString) { + if (!this._inited) { + throw new Error("MoatRPC: Not initialized"); + } + + const proxyType = this._meekTransport._meekProxyType; + const proxyAddress = this._meekTransport._meekProxyAddress; + const proxyPort = this._meekTransport._meekProxyPort; + const proxyUsername = this._meekTransport._meekProxyUsername; + const proxyPassword = this._meekTransport._meekProxyPassword; + + const proxyPS = Cc[ + "@mozilla.org/network/protocol-proxy-service;1" + ].getService(Ci.nsIProtocolProxyService); + const flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST; + const noTimeout = 0xffffffff; // UINT32_MAX + const proxyInfo = proxyPS.newProxyInfoWithAuth( + proxyType, + proxyAddress, + proxyPort, + proxyUsername, + proxyPassword, + undefined, + undefined, + flags, + noTimeout, + undefined + ); + + const uri = Services.io.newURI(uriString); + // There does not seem to be a way to directly create an nsILoadInfo from + // JavaScript, so we create a throw away non-proxied channel to get one. + const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + const loadInfo = Services.io.newChannelFromURI( + uri, + undefined, + Services.scriptSecurityManager.getSystemPrincipal(), + undefined, + secFlags, + Ci.nsIContentPolicy.TYPE_OTHER + ).loadInfo; + + const httpHandler = Services.io + .getProtocolHandler("http") + .QueryInterface(Ci.nsIHttpProtocolHandler); + const ch = httpHandler + .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo) + .QueryInterface(Ci.nsIHttpChannel); + + // remove all headers except for 'Host" + const headers = []; + ch.visitRequestHeaders({ + visitHeader: (key, val) => { + if (key !== "Host") { + headers.push(key); + } + }, + }); + headers.forEach(key => ch.setRequestHeader(key, "", false)); + + return ch; + } + + async _makeRequest(procedure, args) { + const procedureURIString = `${Services.prefs.getStringPref( + TorLauncherPrefs.moat_service + )}/${procedure}`; + const ch = this._makeHttpHandler(procedureURIString); + + // Arrange for the POST data to be sent. + const argsJson = JSON.stringify(args); + + const inStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + inStream.setData(argsJson, argsJson.length); + const upChannel = ch.QueryInterface(Ci.nsIUploadChannel); + const contentType = "application/vnd.api+json"; + upChannel.setUploadStream(inStream, contentType, argsJson.length); + ch.requestMethod = "POST"; + + // Make request + const listener = new MoatResponseListener(); + await ch.asyncOpen(listener, ch); + + // wait for response + const responseJSON = await listener.response(); + + // parse that JSON + return JSON.parse(responseJSON); + } + + async testInternetConnection() { + const uri = `${Services.prefs.getStringPref( + TorLauncherPrefs.moat_service + )}/circumvention/countries`; + const ch = this._makeHttpHandler(uri); + ch.requestMethod = "HEAD"; + + const listener = new InternetTestResponseListener(); + await ch.asyncOpen(listener, ch); + return listener.status(); + } + + // + // Moat APIs + // + + // Receive a CAPTCHA challenge, takes the following parameters: + // - transports: array of transport strings available to us eg: ["obfs4", "meek"] + // + // returns an object with the following fields: + // - transport: a transport string the moat server decides it will send you selected + // from the list of provided transports + // - image: a base64 encoded jpeg with the captcha to complete + // - challenge: a nonce/cookie string associated with this request + async fetch(transports) { + if ( + // ensure this is an array + Array.isArray(transports) && + // ensure array has values + !!transports.length && + // ensure each value in the array is a string + transports.reduce((acc, cur) => acc && typeof cur === "string", true) + ) { + const args = { + data: [ + { + version: "0.1.0", + type: "client-transports", + supported: transports, + }, + ], + }; + const response = await this._makeRequest("fetch", args); + if ("errors" in response) { + const code = response.errors[0].code; + const detail = response.errors[0].detail; + throw new Error(`MoatRPC: ${detail} (${code})`); + } + + const transport = response.data[0].transport; + const image = response.data[0].image; + const challenge = response.data[0].challenge; + + return { transport, image, challenge }; + } + throw new Error("MoatRPC: fetch() expects a non-empty array of strings"); + } + + // Submit an answer for a CAPTCHA challenge and get back bridges, takes the following + // parameters: + // - transport: the transport string associated with a previous fetch request + // - challenge: the nonce string associated with the fetch request + // - solution: solution to the CAPTCHA associated with the fetch request + // - qrcode: true|false whether we want to get back a qrcode containing the bridge strings + // + // returns an object with the following fields: + // - bridges: an array of bridge line strings + // - qrcode: base64 encoded jpeg of bridges if requested, otherwise null + // if the provided solution is incorrect, returns an empty object + async check(transport, challenge, solution, qrcode) { + const args = { + data: [ + { + id: "2", + version: "0.1.0", + type: "moat-solution", + transport, + challenge, + solution, + qrcode: qrcode ? "true" : "false", + }, + ], + }; + const response = await this._makeRequest("check", args); + if ("errors" in response) { + const code = response.errors[0].code; + const detail = response.errors[0].detail; + if (code == 419 && detail === "The CAPTCHA solution was incorrect.") { + return {}; + } + + throw new Error(`MoatRPC: ${detail} (${code})`); + } + + const bridges = response.data[0].bridges; + const qrcodeImg = qrcode ? response.data[0].qrcode : null; + + return { bridges, qrcode: qrcodeImg }; + } + + // Convert received settings object to format used by TorSettings module + // In the event of error, just return null + _fixupSettings(settings) { + try { + let retval = TorSettings.defaultSettings(); + if ("bridges" in settings) { + retval.bridges.enabled = true; + switch (settings.bridges.source) { + case "builtin": + retval.bridges.source = TorBridgeSource.BuiltIn; + retval.bridges.builtin_type = settings.bridges.type; + // Tor Browser will periodically update the built-in bridge strings list using the + // circumvention_builtin() function, so we can ignore the bridge strings we have received here; + // BridgeDB only returns a subset of the available built-in bridges through the circumvention_settings() + // function which is fine for our 3rd parties, but we're better off ignoring them in Tor Browser, otherwise + // we get in a weird situation of needing to update our built-in bridges in a piece-meal fashion which + // seems over-complicated/error-prone + break; + case "bridgedb": + retval.bridges.source = TorBridgeSource.BridgeDB; + if (settings.bridges.bridge_strings) { + retval.bridges.bridge_strings = settings.bridges.bridge_strings; + retval.bridges.disabled_strings = []; + } else { + throw new Error( + "MoatRPC::_fixupSettings(): Received no bridge-strings for BridgeDB bridge source" + ); + } + break; + default: + throw new Error( + `MoatRPC::_fixupSettings(): Unexpected bridge source '${settings.bridges.source}'` + ); + } + } + if ("proxy" in settings) { + // TODO: populate proxy settings + } + if ("firewall" in settings) { + // TODO: populate firewall settings + } + return retval; + } catch (ex) { + console.log(ex.message); + return null; + } + } + + // Converts a list of settings objects received from BridgeDB to a list of settings objects + // understood by the TorSettings module + // In the event of error, returns and empty list + _fixupSettingsList(settingsList) { + try { + let retval = []; + for (let settings of settingsList) { + settings = this._fixupSettings(settings); + if (settings != null) { + retval.push(settings); + } + } + return retval; + } catch (ex) { + console.log(ex.message); + return []; + } + } + + // Request tor settings for the user optionally based on their location (derived + // from their IP), takes the following parameters: + // - transports: optional, an array of transports available to the client; if empty (or not + // given) returns settings using all working transports known to the server + // - country: optional, an ISO 3166-1 alpha-2 country code to request settings for; + // if not provided the country is determined by the user's IP address + // + // returns an array of settings objects in roughly the same format as the _settings + // object on the TorSettings module. + // - If the server cannot determine the user's country (and no country code is provided), + // then null is returned + // - If the country has no associated settings, an empty array is returned + async circumvention_settings(transports, country) { + const args = { + transports: transports ? transports : [], + country, + }; + const response = await this._makeRequest("circumvention/settings", args); + if ("errors" in response) { + const code = response.errors[0].code; + const detail = response.errors[0].detail; + if (code == 406) { + console.log( + "MoatRPC::circumvention_settings(): Cannot automatically determine user's country-code" + ); + // cannot determine user's country + return null; + } + + throw new Error(`MoatRPC: ${detail} (${code})`); + } else if ("settings" in response) { + return this._fixupSettingsList(response.settings); + } + + return []; + } + + // Request a list of country codes with available censorship circumvention settings + // + // returns an array of ISO 3166-1 alpha-2 country codes which we can query settings + // for + async circumvention_countries() { + const args = {}; + return this._makeRequest("circumvention/countries", args); + } + + // Request a copy of the builtin bridges, takes the following parameters: + // - transports: optional, an array of transports we would like the latest bridge strings + // for; if empty (or not given) returns all of them + // + // returns a map whose keys are pluggable transport types and whose values are arrays of + // bridge strings for that type + async circumvention_builtin(transports) { + const args = { + transports: transports ? transports : [], + }; + const response = await this._makeRequest("circumvention/builtin", args); + if ("errors" in response) { + const code = response.errors[0].code; + const detail = response.errors[0].detail; + throw new Error(`MoatRPC: ${detail} (${code})`); + } + + let map = new Map(); + for (const [transport, bridge_strings] of Object.entries(response)) { + map.set(transport, bridge_strings); + } + + return map; + } +} diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm new file mode 100644 index 0000000000000..13c1f54d2ee97 --- /dev/null +++ b/browser/modules/TorConnect.jsm @@ -0,0 +1,755 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState", "TorCensorshipLevel"]; + +const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" +); + +const { setTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +const { BrowserWindowTracker } = ChromeUtils.import( + "resource:///modules/BrowserWindowTracker.jsm" +); + +const { TorProtocolService, TorProcessStatus, TorTopics, TorBootstrapRequest } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +const { TorLauncherUtil } = ChromeUtils.import( + "resource://torlauncher/modules/tl-util.jsm" +); + +const { TorSettings, TorSettingsTopics, TorBridgeSource, TorBuiltinBridgeTypes, TorProxyType } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm"); + +/* Browser observer topis */ +const BrowserTopics = Object.freeze({ + ProfileAfterChange: "profile-after-change", +}); + +/* Relevant prefs used by tor-launcher */ +const TorLauncherPrefs = Object.freeze({ + prompt_at_startup: "extensions.torlauncher.prompt_at_startup", +}); + +const TorConnectPrefs = Object.freeze({ + censorship_level: "torbrowser.debug.censorship_level", +}); + +const TorConnectState = Object.freeze({ + /* Our initial state */ + Initial: "Initial", + /* In-between initial boot and bootstrapping, users can change tor network settings during this state */ + Configuring: "Configuring", + /* Tor is attempting to bootstrap with settings from censorship-circumvention db */ + AutoBootstrapping: "AutoBootstrapping", + /* Tor is bootstrapping */ + Bootstrapping: "Bootstrapping", + /* Passthrough state back to Configuring */ + Error: "Error", + /* Final state, after successful bootstrap */ + Bootstrapped: "Bootstrapped", + /* If we are using System tor or the legacy Tor-Launcher */ + Disabled: "Disabled", +}); + +const TorCensorshipLevel = Object.freeze({ + /* No censorship detected */ + None: 0, + /* Moderate censorship detected, autobootstrap may evade it */ + Moderate: 1, + /* Severe censorship detected, but connection may still succeed */ + Severe: 2, + /* Extreme censorship detected, connection will always end in an error */ + Extreme: 3, +}); + +/* + TorConnect State Transitions + + ┌─────────┐ ┌────────┐ + │ ▼ ▼ │ + │ ┌──────────────────────────────────────────────────────────┐ │ + ┌─┼────── │ Error │ ◀───┐ │ + │ │ └──────────────────────────────────────────────────────────┘ │ │ + │ │ ▲ │ │ + │ │ │ │ │ + │ │ │ │ │ + │ │ ┌───────────────────────┐ ┌──────────┐ │ │ + │ │ ┌──── │ Initial │ ────────────────────▶ │ Disabled │ │ │ + │ │ │ └───────────────────────┘ └──────────┘ │ │ + │ │ │ │ │ │ + │ │ │ │ beginBootstrap() │ │ + │ │ │ ▼ │ │ + │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + │ │ │ │ Bootstrapping │ ────┘ │ + │ │ │ └──────────────────────────────────────────────────────────┘ │ + │ │ │ │ ▲ │ │ + │ │ │ │ cancelBootstrap() │ beginBootstrap() └────┐ │ + │ │ │ ▼ │ │ │ + │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + │ │ └───▶ │ │ ─┼────┘ + │ │ │ │ │ + │ │ │ │ │ + │ │ │ Configuring │ │ + │ │ │ │ │ + │ │ │ │ │ + └─┼─────▶ │ │ │ + │ └──────────────────────────────────────────────────────────┘ │ + │ │ ▲ │ + │ │ beginAutoBootstrap() │ cancelAutoBootstrap() │ + │ ▼ │ │ + │ ┌───────────────────────┐ │ │ + └────── │ AutoBootstrapping │ ─┘ │ + └───────────────────────┘ │ + │ │ + │ │ + ▼ │ + ┌───────────────────────┐ │ + │ Bootstrapped │ ◀───────────────────────────────────┘ + └───────────────────────┘ +*/ + +/* Maps allowed state transitions + TorConnectStateTransitions[state] maps to an array of allowed states to transition to + This is just an encoding of the above transition diagram that we verify at runtime +*/ +const TorConnectStateTransitions = + Object.freeze(new Map([ + [TorConnectState.Initial, + [TorConnectState.Disabled, + TorConnectState.Bootstrapping, + TorConnectState.Configuring, + TorConnectState.Error]], + [TorConnectState.Configuring, + [TorConnectState.AutoBootstrapping, + TorConnectState.Bootstrapping, + TorConnectState.Error]], + [TorConnectState.AutoBootstrapping, + [TorConnectState.Configuring, + TorConnectState.Bootstrapped, + TorConnectState.Error]], + [TorConnectState.Bootstrapping, + [TorConnectState.Configuring, + TorConnectState.Bootstrapped, + TorConnectState.Error]], + [TorConnectState.Error, + [TorConnectState.Configuring]], + // terminal states + [TorConnectState.Bootstrapped, []], + [TorConnectState.Disabled, []], + ])); + +/* Topics Notified by the TorConnect module */ +const TorConnectTopics = Object.freeze({ + StateChange: "torconnect:state-change", + BootstrapProgress: "torconnect:bootstrap-progress", + BootstrapComplete: "torconnect:bootstrap-complete", + BootstrapError: "torconnect:bootstrap-error", +}); + +// The StateCallback is a wrapper around an async function which executes during +// the lifetime of a TorConnect State. A system is also provided to allow this +// ongoing function to early-out via a per StateCallback on_transition callback +// which may be called externally when we need to early-out and move on to another +// state (for example, from Bootstrapping to Configuring in the event the user +// cancels a bootstrap attempt) +class StateCallback { + + constructor(state, callback) { + this._state = state; + this._callback = callback; + this._init(); + } + + _init() { + // this context object is bound to the callback each time transition is + // attempted via begin() + this._context = { + // This callback may be overwritten in the _callback for each state + // States may have various pieces of work which need to occur + // before they can be exited (eg resource cleanup) + // See the _stateCallbacks map for examples + on_transition: (nextState) => {}, + + // flag used to determine if a StateCallback should early-out + // its work + _transitioning: false, + + // may be called within the StateCallback to determine if exit is possible + get transitioning() { + return this._transitioning; + } + }; + } + + async begin(...args) { + console.log(`TorConnect: Entering ${this._state} state`); + this._init(); + try { + // this Promise will block until this StateCallback has completed its work + await Promise.resolve(this._callback.call(this._context, ...args)); + console.log(`TorConnect: Exited ${this._state} state`); + + // handled state transition + Services.obs.notifyObservers({state: this._nextState}, TorConnectTopics.StateChange); + TorConnect._callback(this._nextState).begin(...this._nextStateArgs); + } catch (obj) { + TorConnect._changeState(TorConnectState.Error, obj?.message, obj?.details); + } + } + + transition(nextState, ...args) { + this._nextState = nextState; + this._nextStateArgs = [...args]; + + // calls the on_transition callback to resolve any async work or do per-state cleanup + // this call to on_transition should resolve the async work currentlying going on in this.begin() + this._context.on_transition(nextState); + this._context._transitioning = true; + } +} + +// async method to sleep for a given amount of time +const debug_sleep = async (ms) => { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); + }); +} + +const TorConnect = (() => { + let retval = { + + _state: TorConnectState.Initial, + _bootstrapProgress: 0, + _bootstrapStatus: null, + _detectedCensorshiplevel: TorCensorshipLevel.None, + // list of country codes Moat has settings for + _countryCodes: [], + _countryNames: Object.freeze((() => { + const codes = Services.intl.getAvailableLocaleDisplayNames("region"); + const names = Services.intl.getRegionDisplayNames(undefined, codes); + let codesNames = {}; + for (let i = 0; i < codes.length; i++) { + codesNames[codes[i]] = names[i]; + } + return codesNames; + })()), + _errorMessage: null, + _errorDetails: null, + _logHasWarningOrError: false, + _transitionPromise: null, + + /* These functions represent ongoing work associated with one of our states + Some of these functions are mostly empty, apart from defining an + on_transition function used to resolve their Promise */ + _stateCallbacks: Object.freeze(new Map([ + /* Initial is never transitioned to */ + [TorConnectState.Initial, new StateCallback(TorConnectState.Initial, async function() { + // The initial state doesn't actually do anything, so here is a skeleton for other + // states which do perform work + await new Promise(async (resolve, reject) => { + // This function is provided to signal to the callback that it is complete. + // It is called as a result of _changeState and at the very least must + // resolve the root Promise object within the StateCallback function + // The on_transition callback may also perform necessary cleanup work + this.on_transition = (nextState) => { + resolve(); + }; + + try { + // each state may have a sequence of async work to do + let asyncWork = async () => {}; + await asyncWork(); + + // after each block we may check for an opportunity to early-out + if (this.transitioning) { + return; + } + + // repeat the above pattern as necessary + } catch(err) { + // any thrown exceptions here will trigger a transition to the Error state + TorConnect._changeState(TorConnectState.Error, err?.message, err?.details); + } + }); + })], + /* Configuring */ + [TorConnectState.Configuring, new StateCallback(TorConnectState.Configuring, async function() { + await new Promise(async (resolve, reject) => { + this.on_transition = (nextState) => { + resolve(); + }; + }); + })], + /* Bootstrapping */ + [TorConnectState.Bootstrapping, new StateCallback(TorConnectState.Bootstrapping, async function() { + // wait until bootstrap completes or we get an error + await new Promise(async (resolve, reject) => { + + // we reset the bootstrap failure count so users can manually try settings without the + // censorship circumvention state machine getting in the way + TorConnect._detectedCensorshipLevel = TorCensorshipLevel.None; + + // debug hook to simulate censorship preventing bootstrapping + if (Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) > 0) { + this.on_transition = (nextState) => { + resolve(); + }; + await debug_sleep(1500); + TorConnect._changeState(TorConnectState.Error, "Bootstrap failed (for debugging purposes)", "Error: Censorship simulation", true); + TorProtocolService._torBootstrapDebugSetError(); + return; + } + + const tbr = new TorBootstrapRequest(); + this.on_transition = async (nextState) => { + if (nextState === TorConnectState.Configuring) { + // stop bootstrap process if user cancelled + await tbr.cancel(); + } + resolve(); + }; + + tbr.onbootstrapstatus = (progress, status) => { + TorConnect._updateBootstrapStatus(progress, status); + }; + tbr.onbootstrapcomplete = () => { + TorConnect._changeState(TorConnectState.Bootstrapped); + }; + tbr.onbootstraperror = (message, details) => { + TorConnect._changeState(TorConnectState.Error, message, details, true); + }; + + tbr.bootstrap(); + }); + })], + /* AutoBootstrapping */ + [TorConnectState.AutoBootstrapping, new StateCallback(TorConnectState.AutoBootstrapping, async function(countryCode) { + await new Promise(async (resolve, reject) => { + this.on_transition = (nextState) => { + resolve(); + }; + + // debug hook to simulate censorship preventing bootstrapping + { + const censorshipLevel = Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0); + if (censorshipLevel > 1) { + this.on_transition = (nextState) => { + resolve(); + }; + // always fail even after manually selecting location specific settings + if (censorshipLevel == 3) { + await debug_sleep(2500); + TorConnect._changeState(TorConnectState.Error, "Error: Extreme Censorship simulation", "", true); + return; + // only fail after auto selecting, manually selecting succeeds + } else if (censorshipLevel == 2 && !countryCode) { + await debug_sleep(2500); + TorConnect._changeState(TorConnectState.Error, "Error: Severe Censorship simulation", "", true); + return; + } + TorProtocolService._torBootstrapDebugSetError(); + } + } + + const throw_error = (message, details) => { + let err = new Error(message); + err.details = details; + throw err; + }; + + // lookup user's potential censorship circumvention settings from Moat service + try { + this.mrpc = new MoatRPC(); + await this.mrpc.init(); + + if (this.transitioning) return; + + this.settings = await this.mrpc.circumvention_settings([...TorBuiltinBridgeTypes, "vanilla"], countryCode); + + if (this.transitioning) return; + + if (this.settings === null) { + // unable to determine country + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry); + } else if (this.settings.length === 0) { + // no settings available for country + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry); + } + + // apply each of our settings and try to bootstrap with each + try { + this.originalSettings = TorSettings.getSettings(); + + for (const [index, currentSetting] of this.settings.entries()) { + + // we want to break here so we can fall through and restore original settings + if (this.transitioning) break; + + console.log(`TorConnect: Attempting Bootstrap with configuration ${index+1}/${this.settings.length}`); + + TorSettings.setSettings(currentSetting); + await TorSettings.applySettings(); + + // build out our bootstrap request + const tbr = new TorBootstrapRequest(); + tbr.onbootstrapstatus = (progress, status) => { + TorConnect._updateBootstrapStatus(progress, status); + }; + tbr.onbootstraperror = (message, details) => { + console.log(`TorConnect: Auto-Bootstrap error => ${message}; ${details}`); + }; + + // update transition callback for user cancel + this.on_transition = async (nextState) => { + if (nextState === TorConnectState.Configuring) { + await tbr.cancel(); + } + resolve(); + }; + + // begin bootstrap + if (await tbr.bootstrap()) { + // persist the current settings to preferences + TorSettings.saveToPrefs(); + TorConnect._changeState(TorConnectState.Bootstrapped); + return; + } + } + + // bootstrapped failed for all potential settings, so reset daemon to use original + TorSettings.setSettings(this.originalSettings); + await TorSettings.applySettings(); + TorSettings.saveToPrefs(); + + // only explicitly change state here if something else has not transitioned us + if (!this.transitioning) { + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.autoBootstrappingAllFailed); + } + return; + } catch (err) { + // restore original settings in case of error + try { + TorSettings.setSettings(this.originalSettings); + await TorSettings.applySettings(); + } catch(err) { + console.log(`TorConnect: Failed to restore original settings => ${err}`); + } + // throw to outer catch to transition us + throw err; + } + } catch(err) { + if (this.mrpc?.inited) { + // lookup countries which have settings available + TorConnect._countryCodes = await this.mrpc.circumvention_countries(); + } + TorConnect._changeState(TorConnectState.Error, err?.message, err?.details, true); + } finally { + // important to uninit MoatRPC object or else the pt process will live as long as tor-browser + this.mrpc?.uninit(); + } + }); + })], + /* Bootstrapped */ + [TorConnectState.Bootstrapped, new StateCallback(TorConnectState.Bootstrapped, async function() { + await new Promise((resolve, reject) => { + // on_transition not defined because no way to leave Bootstrapped state + // notify observers of bootstrap completion + Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete); + }); + })], + /* Error */ + [TorConnectState.Error, new StateCallback(TorConnectState.Error, async function(errorMessage, errorDetails, bootstrappingFailure) { + await new Promise((resolve, reject) => { + this.on_transition = async(nextState) => { + resolve(); + }; + + TorConnect._errorMessage = errorMessage; + TorConnect._errorDetails = errorDetails; + + if (bootstrappingFailure && TorConnect._detectedCensorshipLevel < TorCensorshipLevel.Extreme) { + TorConnect._detectedCensorshipLevel += 1; + } + + Services.obs.notifyObservers({message: errorMessage, details: errorDetails, censorshipLevel: TorConnect.detectedCensorshipLevel}, TorConnectTopics.BootstrapError); + + TorConnect._changeState(TorConnectState.Configuring); + }); + })], + /* Disabled */ + [TorConnectState.Disabled, new StateCallback(TorConnectState.Disabled, async function() { + await new Promise((resolve, reject) => { + // no-op, on_transition not defined because no way to leave Disabled state + }); + })], + ])), + + _callback: function(state) { + return this._stateCallbacks.get(state); + }, + + _changeState: function(newState, ...args) { + const prevState = this._state; + + // ensure this is a valid state transition + if (!TorConnectStateTransitions.get(prevState)?.includes(newState)) { + throw Error(`TorConnect: Attempted invalid state transition from ${prevState} to ${newState}`); + } + + console.log(`TorConnect: Try transitioning from ${prevState} to ${newState}`); + + // set our new state first so that state transitions can themselves trigger + // a state transition + this._state = newState; + + // call our state function and forward any args + this._callback(prevState).transition(newState, ...args); + }, + + _updateBootstrapStatus: function(progress, status) { + this._bootstrapProgress= progress; + this._bootstrapStatus = status; + + console.log(`TorConnect: Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`); + Services.obs.notifyObservers({ + progress: TorConnect._bootstrapProgress, + status: TorConnect._bootstrapStatus, + hasWarnings: TorConnect._logHasWarningOrError + }, TorConnectTopics.BootstrapProgress); + }, + + // init should be called on app-startup in MainProcessingSingleton.jsm + init: function() { + console.log("TorConnect: init()"); + + // delay remaining init until after profile-after-change + Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange); + + this._callback(TorConnectState.Initial).begin(); + }, + + observe: async function(subject, topic, data) { + console.log(`TorConnect: Observed ${topic}`); + + switch(topic) { + + /* Determine which state to move to from Initial */ + case BrowserTopics.ProfileAfterChange: { + if (TorLauncherUtil.useLegacyLauncher || !TorProtocolService.ownsTorDaemon) { + // Disabled + this._changeState(TorConnectState.Disabled); + } else { + let observeTopic = (topic) => { + Services.obs.addObserver(this, topic); + console.log(`TorConnect: Observing topic '${topic}'`); + }; + + // register the Tor topics we always care about + observeTopic(TorTopics.ProcessExited); + observeTopic(TorTopics.LogHasWarnOrErr); + observeTopic(TorSettingsTopics.Ready); + } + Services.obs.removeObserver(this, topic); + break; + } + /* We need to wait until TorSettings have been loaded and applied before we can Quickstart */ + case TorSettingsTopics.Ready: { + if (this.shouldQuickStart) { + // Quickstart + this._changeState(TorConnectState.Bootstrapping); + } else { + // Configuring + this._changeState(TorConnectState.Configuring); + } + break; + } + case TorTopics.LogHasWarnOrErr: { + this._logHasWarningOrError = true; + break; + } + default: + // ignore + break; + } + }, + + /* + Various getters + */ + + get shouldShowTorConnect() { + // TorBrowser must control the daemon + return (TorProtocolService.ownsTorDaemon && + // and we're not using the legacy launcher + !TorLauncherUtil.useLegacyLauncher && + // if we have succesfully bootstraped, then no need to show TorConnect + this.state != TorConnectState.Bootstrapped); + }, + + get shouldQuickStart() { + // quickstart must be enabled + return TorSettings.quickstart.enabled && + // and the previous bootstrap attempt must have succeeded + !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true); + }, + + get state() { + return this._state; + }, + + get bootstrapProgress() { + return this._bootstrapProgress; + }, + + get bootstrapStatus() { + return this._bootstrapStatus; + }, + + get detectedCensorshipLevel() { + return this._detectedCensorshipLevel; + }, + + get errorMessage() { + return this._errorMessage; + }, + + get errorDetails() { + return this._errorDetails; + }, + + get logHasWarningOrError() { + return this._logHasWarningOrError; + }, + + get countryCodes() { + return this._countryCodes; + }, + + get countryNames() { + return this._countryNames; + }, + + /* + These functions allow external consumers to tell TorConnect to transition states + */ + + beginBootstrap: function() { + console.log("TorConnect: beginBootstrap()"); + this._changeState(TorConnectState.Bootstrapping); + }, + + cancelBootstrap: function() { + console.log("TorConnect: cancelBootstrap()"); + this._changeState(TorConnectState.Configuring); + }, + + beginAutoBootstrap: function(countryCode) { + console.log("TorConnect: beginAutoBootstrap()"); + this._changeState(TorConnectState.AutoBootstrapping, countryCode); + }, + + cancelAutoBootstrap: function() { + console.log("TorConnect: cancelAutoBootstrap()"); + this._changeState(TorConnectState.Configuring); + }, + + /* + Further external commands and helper methods + */ + openTorPreferences: function() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:preferences#connection", true); + }, + + openTorConnect: function() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:torconnect", true, {ignoreQueryString: true}); + }, + + viewTorLogs: function() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:preferences#connection-viewlogs", true); + }, + + getCountryCodes: async function() { + // Difference with the getter: this is to be called by TorConnectParent, and downloads + // the country codes if they are not already in cache. + if (this._countryCodes.length) { + return this._countryCodes; + } + const mrpc = new MoatRPC(); + try { + await mrpc.init(); + this._countryCodes = await mrpc.circumvention_countries(); + } catch(err) { + console.log("An error occurred while fetching country codes", err); + } finally { + mrpc.uninit(); + } + return this._countryCodes; + }, + + getRedirectURL: function(url) { + return `about:torconnect?redirect=${encodeURIComponent(url)}`; + }, + + // called from browser.js on browser startup, passed in either the user's homepage(s) + // or uris passed via command-line; we want to replace them with about:torconnect uris + // which redirect after bootstrapping + getURIsToLoad: function(uriVariant) { + // convert the object we get from browser.js + let uriStrings = ((v) => { + // an interop array + if (v instanceof Ci.nsIArray) { + // Transform the nsIArray of nsISupportsString's into a JS Array of + // JS strings. + return Array.from( + v.enumerate(Ci.nsISupportsString), + supportStr => supportStr.data + ); + // an interop string + } else if (v instanceof Ci.nsISupportsString) { + return [v.data]; + // a js string + } else if (typeof v === "string") { + return v.split("|"); + // a js array of js strings + } else if (Array.isArray(v) && + v.reduce((allStrings, entry) => {return allStrings && (typeof entry === "string");}, true)) { + return v; + } + // about:tor as safe fallback + console.log(`TorConnect: getURIsToLoad() received unknown variant '${JSON.stringify(v)}'`); + return ["about:tor"]; + })(uriVariant); + + // will attempt to convert user-supplied string to a uri, fallback to about:tor if cannot convert + // to valid uri object + let uriStringToUri = (uriString) => { + const fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_NONE; + let uri = Services.uriFixup.getFixupURIInfo(uriString, fixupFlags) + .preferredURI; + return uri ? uri : Services.io.newURI("about:tor"); + }; + let uris = uriStrings.map(uriStringToUri); + + // assume we have a valid uri and generate an about:torconnect redirect uri + let redirectUrls = uris.map((uri) => this.getRedirectURL(uri.spec)); + + console.log(`TorConnect: Will load after bootstrap => [${uris.map((uri) => {return uri.spec;}).join(", ")}]`); + return redirectUrls; + }, + }; + retval.init(); + return retval; +})(); /* TorConnect */ diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm new file mode 100644 index 0000000000000..aa27e13e11711 --- /dev/null +++ b/browser/modules/TorProtocolService.jsm @@ -0,0 +1,502 @@ +// Copyright (c) 2021, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = [ + "TorProtocolService", + "TorProcessStatus", + "TorTopics", + "TorBootstrapRequest", +]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +const { TorLauncherUtil } = ChromeUtils.import( + "resource://torlauncher/modules/tl-util.jsm" +); + +// see tl-process.js +const TorProcessStatus = Object.freeze({ + Unknown: 0, + Starting: 1, + Running: 2, + Exited: 3, +}); + +/* tor-launcher observer topics */ +const TorTopics = Object.freeze({ + BootstrapStatus: "TorBootstrapStatus", + BootstrapError: "TorBootstrapError", + ProcessExited: "TorProcessExited", + LogHasWarnOrErr: "TorLogHasWarnOrErr", +}); + +/* Browser observer topis */ +const BrowserTopics = Object.freeze({ + ProfileAfterChange: "profile-after-change", +}); + +var TorProtocolService = { + _TorLauncherProtocolService: null, + _TorProcessService: null, + + // maintain a map of tor settings set by Tor Browser so that we don't + // repeatedly set the same key/values over and over + // this map contains string keys to primitive or array values + _settingsCache: new Map(), + + init() { + Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange); + }, + + observe(subject, topic, data) { + if (topic === BrowserTopics.ProfileAfterChange) { + // we have to delay init'ing this or else the crypto service inits too early without a profile + // which breaks the password manager + this._TorLauncherProtocolService = Cc[ + "@torproject.org/torlauncher-protocol-service;1" + ].getService(Ci.nsISupports).wrappedJSObject; + this._TorProcessService = Cc[ + "@torproject.org/torlauncher-process-service;1" + ].getService(Ci.nsISupports).wrappedJSObject; + Services.obs.removeObserver(this, topic); + } + }, + + _typeof(aValue) { + switch (typeof aValue) { + case "boolean": + return "boolean"; + case "string": + return "string"; + case "object": + if (aValue == null) { + return "null"; + } else if (Array.isArray(aValue)) { + return "array"; + } + return "object"; + } + return "unknown"; + }, + + _assertValidSettingKey(aSetting) { + // ensure the 'key' is a string + if (typeof aSetting != "string") { + throw new Error( + `Expected setting of type string but received ${typeof aSetting}` + ); + } + }, + + _assertValidSetting(aSetting, aValue) { + this._assertValidSettingKey(aSetting); + + const valueType = this._typeof(aValue); + switch (valueType) { + case "boolean": + case "string": + case "null": + return; + case "array": + for (const element of aValue) { + if (typeof element != "string") { + throw new Error( + `Setting '${aSetting}' array contains value of invalid type '${typeof element}'` + ); + } + } + return; + default: + throw new Error( + `Invalid object type received for setting '${aSetting}'` + ); + } + }, + + // takes a Map containing tor settings + // throws on error + async writeSettings(aSettingsObj) { + // only write settings that have changed + let newSettings = new Map(); + for (const [setting, value] of aSettingsObj) { + let saveSetting = false; + + // make sure we have valid data here + this._assertValidSetting(setting, value); + + if (!this._settingsCache.has(setting)) { + // no cached setting, so write + saveSetting = true; + } else { + const cachedValue = this._settingsCache.get(setting); + if (value != cachedValue) { + // compare arrays member-wise + if (Array.isArray(value) && Array.isArray(cachedValue)) { + if (value.length != cachedValue.length) { + saveSetting = true; + } else { + const arrayLength = value.length; + for (let i = 0; i < arrayLength; ++i) { + if (value[i] != cachedValue[i]) { + saveSetting = true; + break; + } + } + } + } else { + // some other different values + saveSetting = true; + } + } + } + + if (saveSetting) { + newSettings.set(setting, value); + } + } + + // only write if new setting to save + if (newSettings.size > 0) { + // convert settingsObject map to js object for torlauncher-protocol-service + let settingsObject = {}; + for (const [setting, value] of newSettings) { + settingsObject[setting] = value; + } + + let errorObject = {}; + if ( + !(await this._TorLauncherProtocolService.TorSetConfWithReply( + settingsObject, + errorObject + )) + ) { + throw new Error(errorObject.details); + } + + // save settings to cache after successfully writing to Tor + for (const [setting, value] of newSettings) { + this._settingsCache.set(setting, value); + } + } + }, + + async _readSetting(aSetting) { + this._assertValidSettingKey(aSetting); + let reply = await this._TorLauncherProtocolService.TorGetConf(aSetting); + if (this._TorLauncherProtocolService.TorCommandSucceeded(reply)) { + return reply.lineArray; + } + throw new Error(reply.lineArray.join("\n")); + }, + + async _readBoolSetting(aSetting) { + let lineArray = await this._readSetting(aSetting); + if (lineArray.length != 1) { + throw new Error( + `Expected an array with length 1 but received array of length ${lineArray.length}` + ); + } + + let retval = lineArray[0]; + switch (retval) { + case "0": + return false; + case "1": + return true; + default: + throw new Error(`Expected boolean (1 or 0) but received '${retval}'`); + } + }, + + async _readStringSetting(aSetting) { + let lineArray = await this._readSetting(aSetting); + if (lineArray.length != 1) { + throw new Error( + `Expected an array with length 1 but received array of length ${lineArray.length}` + ); + } + return lineArray[0]; + }, + + async _readStringArraySetting(aSetting) { + let lineArray = await this._readSetting(aSetting); + return lineArray; + }, + + async readBoolSetting(aSetting) { + let value = await this._readBoolSetting(aSetting); + this._settingsCache.set(aSetting, value); + return value; + }, + + async readStringSetting(aSetting) { + let value = await this._readStringSetting(aSetting); + this._settingsCache.set(aSetting, value); + return value; + }, + + async readStringArraySetting(aSetting) { + let value = await this._readStringArraySetting(aSetting); + this._settingsCache.set(aSetting, value); + return value; + }, + + // writes current tor settings to disk + async flushSettings() { + await this.sendCommand("SAVECONF"); + }, + + getLog(countObj) { + countObj = countObj || { value: 0 }; + let torLog = this._TorLauncherProtocolService.TorGetLog(countObj); + return torLog; + }, + + // true if we launched and control tor, false if using system tor + get ownsTorDaemon() { + return TorLauncherUtil.shouldStartAndOwnTor; + }, + + // Assumes `ownsTorDaemon` is true + isNetworkDisabled() { + const reply = TorProtocolService._TorLauncherProtocolService.TorGetConfBool( + "DisableNetwork", + true + ); + if ( + TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply) + ) { + return reply.retVal; + } + return true; + }, + + async enableNetwork() { + let settings = {}; + settings.DisableNetwork = false; + let errorObject = {}; + if ( + !(await this._TorLauncherProtocolService.TorSetConfWithReply( + settings, + errorObject + )) + ) { + throw new Error(errorObject.details); + } + }, + + async sendCommand(cmd) { + return this._TorLauncherProtocolService.TorSendCommand(cmd); + }, + + retrieveBootstrapStatus() { + return this._TorLauncherProtocolService.TorRetrieveBootstrapStatus(); + }, + + _GetSaveSettingsErrorMessage(aDetails) { + try { + return TorLauncherUtil.getSaveSettingsErrorMessage(aDetails); + } catch (e) { + console.log("GetSaveSettingsErrorMessage error", e); + return "Unexpected Error"; + } + }, + + async setConfWithReply(settings) { + let result = false; + const error = {}; + try { + result = await this._TorLauncherProtocolService.TorSetConfWithReply( + settings, + error + ); + } catch (e) { + console.log("TorSetConfWithReply error", e); + error.details = this._GetSaveSettingsErrorMessage(e.message); + } + return { result, error }; + }, + + isBootstrapDone() { + return this._TorProcessService.mIsBootstrapDone; + }, + + clearBootstrapError() { + return this._TorProcessService.TorClearBootstrapError(); + }, + + torBootstrapErrorOccurred() { + return this._TorProcessService.TorBootstrapErrorOccurred; + }, + + _torBootstrapDebugSetError() { + this._TorProcessService._TorSetBootstrapErrorForDebug(); + }, + + // Resolves to null if ok, or an error otherwise + async connect() { + const kTorConfKeyDisableNetwork = "DisableNetwork"; + const settings = {}; + settings[kTorConfKeyDisableNetwork] = false; + const { result, error } = await this.setConfWithReply(settings); + if (!result) { + return error; + } + try { + await this.sendCommand("SAVECONF"); + this.clearBootstrapError(); + this.retrieveBootstrapStatus(); + } catch (e) { + return error; + } + return null; + }, + + torLogHasWarnOrErr() { + return this._TorLauncherProtocolService.TorLogHasWarnOrErr; + }, + + async torStopBootstrap() { + // Tell tor to disable use of the network; this should stop the bootstrap + // process. + const kErrorPrefix = "Setting DisableNetwork=1 failed: "; + try { + let settings = {}; + settings.DisableNetwork = true; + const { result, error } = await this.setConfWithReply(settings); + if (!result) { + console.log( + `Error stopping bootstrap ${kErrorPrefix} ${error.details}` + ); + } + } catch (e) { + console.log(`Error stopping bootstrap ${kErrorPrefix} ${e}`); + } + this.retrieveBootstrapStatus(); + }, + + get torProcessStatus() { + if (this._TorProcessService) { + return this._TorProcessService.TorProcessStatus; + } + return TorProcessStatus.Unknown; + }, +}; +TorProtocolService.init(); + +// modeled after XMLHttpRequest +// nicely encapsulates the observer register/unregister logic +class TorBootstrapRequest { + constructor() { + // number of ms to wait before we abandon the bootstrap attempt + // a value of 0 implies we never wait + this.timeout = 0; + // callbacks for bootstrap process status updates + this.onbootstrapstatus = (progress, status) => {}; + this.onbootstrapcomplete = () => {}; + this.onbootstraperror = (message, details) => {}; + + // internal resolve() method for bootstrap + this._bootstrapPromiseResolve = null; + this._bootstrapPromise = null; + this._timeoutID = null; + } + + async observe(subject, topic, data) { + const obj = subject?.wrappedJSObject; + switch (topic) { + case TorTopics.BootstrapStatus: { + const progress = obj.PROGRESS; + const status = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG"); + if (this.onbootstrapstatus) { + this.onbootstrapstatus(progress, status); + } + if (progress === 100) { + if (this.onbootstrapcomplete) { + this.onbootstrapcomplete(); + } + this._bootstrapPromiseResolve(true); + clearTimeout(this._timeoutID); + } + + break; + } + case TorTopics.BootstrapError: { + // first stop our bootstrap timeout before handling the error + clearTimeout(this._timeoutID); + + await TorProtocolService.torStopBootstrap(); + + const message = obj.message; + const details = obj.details; + if (this.onbootstraperror) { + this.onbootstraperror(message, details); + } + this._bootstrapPromiseResolve(false); + break; + } + } + } + + // resolves 'true' if bootstrap succeeds, false otherwise + async bootstrap() { + if (this._bootstrapPromise) { + return this._bootstrapPromise; + } + + this._bootstrapPromise = new Promise(async (resolve, reject) => { + this._bootstrapPromiseResolve = resolve; + + // register ourselves to listen for bootstrap events + Services.obs.addObserver(this, TorTopics.BootstrapStatus); + Services.obs.addObserver(this, TorTopics.BootstrapError); + + // optionally cancel bootstrap after a given timeout + if (this.timeout > 0) { + this._timeoutID = setTimeout(async () => { + await TorProtocolService.torStopBootstrap(); + if (this.onbootstraperror) { + this.onbootstraperror( + "Tor Bootstrap process timed out", + `Bootstrap attempt abandoned after waiting ${this.timeout} ms` + ); + } + this._bootstrapPromiseResolve(false); + }, this.timeout); + } + + // wait for bootstrapping to begin and maybe handle error + let err = await TorProtocolService.connect(); + if (err) { + clearTimeout(this._timeoutID); + await TorProtocolService.torStopBootstrap(); + + const message = err.message; + const details = err.details; + if (this.onbootstraperror) { + this.onbootstraperror(message, details); + } + this._bootstrapPromiseResolve(false); + } + }).finally(() => { + // and remove ourselves once bootstrap is resolved + Services.obs.removeObserver(this, TorTopics.BootstrapStatus); + Services.obs.removeObserver(this, TorTopics.BootstrapError); + }); + + return this._bootstrapPromise; + } + + async cancel() { + clearTimeout(this._timeoutID); + + await TorProtocolService.torStopBootstrap(); + + this._bootstrapPromiseResolve(false); + } +} diff --git a/browser/modules/TorSettings.jsm b/browser/modules/TorSettings.jsm new file mode 100644 index 0000000000000..41638f9cbd1b6 --- /dev/null +++ b/browser/modules/TorSettings.jsm @@ -0,0 +1,674 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["TorSettings", "TorSettingsTopics", "TorSettingsData", "TorBridgeSource", "TorBuiltinBridgeTypes", "TorProxyType"]; + +const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" +); + +const { TorProtocolService, TorProcessStatus } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +/* Browser observer topics */ +const BrowserTopics = Object.freeze({ + ProfileAfterChange: "profile-after-change", +}); + +/* tor-launcher observer topics */ +const TorTopics = Object.freeze({ + ProcessIsReady: "TorProcessIsReady", +}); + +/* TorSettings observer topics */ +const TorSettingsTopics = Object.freeze({ + Ready: "torsettings:ready", + SettingChanged: "torsettings:setting-changed", +}); + +/* TorSettings observer data (for SettingChanged topic) */ +const TorSettingsData = Object.freeze({ + QuickStartEnabled : "torsettings:quickstart_enabled", +}); + +/* Prefs used to store settings in TorBrowser prefs */ +const TorSettingsPrefs = Object.freeze({ + /* bool: are we pulling tor settings from the preferences */ + enabled: 'torbrowser.settings.enabled', + quickstart : { + /* bool: does tor connect automatically on launch */ + enabled: 'torbrowser.settings.quickstart.enabled', + }, + bridges : { + /* bool: does tor use bridges */ + enabled : 'torbrowser.settings.bridges.enabled', + /* int: -1=invalid|0=builtin|1=bridge_db|2=user_provided */ + source : 'torbrowser.settings.bridges.source', + /* string: obfs4|meek_azure|snowflake|etc */ + builtin_type : 'torbrowser.settings.bridges.builtin_type', + /* preference branch: each child branch should be a bridge string */ + bridge_strings : 'torbrowser.settings.bridges.bridge_strings', + }, + proxy : { + /* bool: does tor use a proxy */ + enabled : 'torbrowser.settings.proxy.enabled', + /* -1=invalid|0=socks4,1=socks5,2=https */ + type: 'torbrowser.settings.proxy.type', + /* string: proxy server address */ + address: 'torbrowser.settings.proxy.address', + /* int: [1,65535], proxy port */ + port: 'torbrowser.settings.proxy.port', + /* string: username */ + username: 'torbrowser.settings.proxy.username', + /* string: password */ + password: 'torbrowser.settings.proxy.password', + }, + firewall : { + /* bool: does tor have a port allow list */ + enabled: 'torbrowser.settings.firewall.enabled', + /* string: comma-delimitted list of port numbers */ + allowed_ports: 'torbrowser.settings.firewall.allowed_ports', + }, +}); + +/* Legacy tor-launcher prefs and pref branches*/ +const TorLauncherPrefs = Object.freeze({ + quickstart: "extensions.torlauncher.quickstart", + default_bridge_type: "extensions.torlauncher.default_bridge_type", + default_bridge: "extensions.torlauncher.default_bridge.", + default_bridge_recommended_type: "extensions.torlauncher.default_bridge_recommended_type", + bridgedb_bridge: "extensions.torlauncher.bridgedb_bridge.", +}); + +/* Config Keys used to configure tor daemon */ +const TorConfigKeys = Object.freeze({ + useBridges: "UseBridges", + bridgeList: "Bridge", + socks4Proxy: "Socks4Proxy", + socks5Proxy: "Socks5Proxy", + socks5ProxyUsername: "Socks5ProxyUsername", + socks5ProxyPassword: "Socks5ProxyPassword", + httpsProxy: "HTTPSProxy", + httpsProxyAuthenticator: "HTTPSProxyAuthenticator", + reachableAddresses: "ReachableAddresses", + clientTransportPlugin: "ClientTransportPlugin", +}); + +const TorBridgeSource = Object.freeze({ + Invalid: -1, + BuiltIn: 0, + BridgeDB: 1, + UserProvided: 2, +}); + +const TorProxyType = Object.freeze({ + Invalid: -1, + Socks4: 0, + Socks5: 1, + HTTPS: 2, +}); + + +const TorBuiltinBridgeTypes = Object.freeze( + (() => { + let bridgeListBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge); + let bridgePrefs = bridgeListBranch.getChildList(""); + + // an unordered set for shoving bridge types into + let bridgeTypes = new Set(); + // look for keys ending in ".N" and treat string before that as the bridge type + const pattern = /.[0-9]+$/; + for (const key of bridgePrefs) { + const offset = key.search(pattern); + if (offset != -1) { + const bt = key.substring(0, offset); + bridgeTypes.add(bt); + } + } + + // recommended bridge type goes first in the list + let recommendedBridgeType = Services.prefs.getCharPref(TorLauncherPrefs.default_bridge_recommended_type, null); + + let retval = []; + if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) { + retval.push(recommendedBridgeType); + } + + for (const bridgeType of bridgeTypes.values()) { + if (bridgeType != recommendedBridgeType) { + retval.push(bridgeType); + } + } + return retval; + })() +); + +/* Parsing Methods */ + +// expects a string representation of an integer from 1 to 65535 +let parsePort = function(aPort) { + // ensure port string is a valid positive integer + const validIntRegex = /^[0-9]+$/; + if (!validIntRegex.test(aPort)) { + return 0; + } + + // ensure port value is on valid range + let port = Number.parseInt(aPort); + if (port < 1 || port > 65535) { + return 0; + } + + return port; +}; +// expects a string in the format: "ADDRESS:PORT" +let parseAddrPort = function(aAddrColonPort) { + let tokens = aAddrColonPort.split(":"); + if (tokens.length != 2) { + return ["", 0]; + } + let address = tokens[0]; + let port = parsePort(tokens[1]); + return [address, port]; +}; + +// expects a string in the format: "USERNAME:PASSWORD" +// split on the first colon and any subsequent go into password +let parseUsernamePassword = function(aUsernameColonPassword) { + let colonIndex = aUsernameColonPassword.indexOf(":"); + if (colonIndex < 0) { + return ["", ""]; + } + + let username = aUsernameColonPassword.substring(0, colonIndex); + let password = aUsernameColonPassword.substring(colonIndex + 1); + + return [username, password]; +}; + +// expects a string in the format: ADDRESS:PORT,ADDRESS:PORT,... +// returns array of ports (as ints) +let parseAddrPortList = function(aAddrPortList) { + let addrPorts = aAddrPortList.split(","); + // parse ADDRESS:PORT string and only keep the port (second element in returned array) + let retval = addrPorts.map(addrPort => parseAddrPort(addrPort)[1]); + return retval; +}; + +// expects a '\n' or '\r\n' delimited bridge string, which we split and trim +// each bridge string can also optionally have 'bridge' at the beginning ie: +// bridge $(type) $(address):$(port) $(certificate) +// we strip out the 'bridge' prefix here +let parseBridgeStrings = function(aBridgeStrings) { + + // replace carriage returns ('\r') with new lines ('\n') + aBridgeStrings = aBridgeStrings.replace(/\r/g, "\n"); + // then replace contiguous new lines ('\n') with a single one + aBridgeStrings = aBridgeStrings.replace(/[\n]+/g, "\n"); + + // split on the newline and for each bridge string: trim, remove starting 'bridge' string + // finally discard entries that are empty strings; empty strings could occur if we receive + // a new line containing only whitespace + let splitStrings = aBridgeStrings.split("\n"); + return splitStrings.map(val => val.trim().replace(/^bridge\s+/i, "")) + .filter(bridgeString => bridgeString != ""); +}; + +// expecting a ',' delimited list of ints with possible white space between +// returns an array of ints +let parsePortList = function(aPortListString) { + let splitStrings = aPortListString.split(","); + // parse and remove duplicates + let portSet = new Set(splitStrings.map(val => parsePort(val.trim()))); + // parsePort returns 0 for failed parses, so remove 0 from list + portSet.delete(0); + return Array.from(portSet); +}; + +let getBuiltinBridgeStrings = function(builtinType) { + if (!builtinType) { + return []; + } + + let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge); + let bridgeBranchPrefs = bridgeBranch.getChildList(""); + let retval = []; + + // regex matches against strings ending in ".N" where N is a positive integer + let pattern = /.[0-9]+$/; + for (const key of bridgeBranchPrefs) { + // verify the location of the match is the correct offset required for aBridgeType + // to fit, and that the string begins with aBridgeType + if (key.search(pattern) == builtinType.length && + key.startsWith(builtinType)) { + let bridgeStr = bridgeBranch.getCharPref(key); + retval.push(bridgeStr); + } + } + + // shuffle so that Tor Browser users don't all try the built-in bridges in the same order + arrayShuffle(retval); + + return retval; +}; + +/* Helper methods */ + +let arrayShuffle = function(array) { + // fisher-yates shuffle + for (let i = array.length - 1; i > 0; --i) { + // number n such that 0.0 <= n < 1.0 + const n = Math.random(); + // integer j such that 0 <= j <= i + const j = Math.floor(n * (i + 1)); + + // swap values at indices i and j + const tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } +} + +let arrayCopy = function(array) { + return [].concat(array); +} + +/* TorSettings module */ + +const TorSettings = (() => { + let self = { + _settings: null, + + // tor daemon related settings + defaultSettings: function() { + let settings = { + quickstart: { + enabled: false + }, + bridges : { + enabled: false, + source: TorBridgeSource.Invalid, + builtin_type: null, + bridge_strings: [], + }, + proxy: { + enabled: false, + type: TorProxyType.Invalid, + address: null, + port: 0, + username: null, + password: null, + }, + firewall: { + enabled: false, + allowed_ports: [], + }, + }; + return settings; + }, + + /* load or init our settings, and register observers */ + init: function() { + if (TorProtocolService.ownsTorDaemon) { + // if the settings branch exists, load settings from prefs + if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) { + this.loadFromPrefs(); + } else { + // otherwise load defaults + this._settings = this.defaultSettings(); + } + Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange); + Services.obs.addObserver(this, TorTopics.ProcessIsReady); + } + }, + + /* wait for relevant life-cycle events to apply saved settings */ + observe: async function(subject, topic, data) { + console.log(`TorSettings: Observed ${topic}`); + + // once the tor daemon is ready, we need to apply our settings + let handleProcessReady = async () => { + // push down settings to tor + await this.applySettings(); + console.log("TorSettings: Ready"); + Services.obs.notifyObservers(null, TorSettingsTopics.Ready); + }; + + switch (topic) { + case BrowserTopics.ProfileAfterChange: { + Services.obs.removeObserver(this, BrowserTopics.ProfileAfterChange); + if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) { + await handleProcessReady(); + } + } + break; + case TorTopics.ProcessIsReady: { + Services.obs.removeObserver(this, TorTopics.ProcessIsReady); + await handleProcessReady(); + } + break; + } + }, + + // load our settings from prefs + loadFromPrefs: function() { + console.log("TorSettings: loadFromPrefs()"); + + let settings = this.defaultSettings(); + + /* Quickstart */ + settings.quickstart.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.quickstart.enabled); + /* Bridges */ + settings.bridges.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.bridges.enabled); + settings.bridges.source = Services.prefs.getIntPref(TorSettingsPrefs.bridges.source, TorBridgeSource.Invalid); + if (settings.bridges.source == TorBridgeSource.BuiltIn) { + let builtinType = Services.prefs.getStringPref(TorSettingsPrefs.bridges.builtin_type); + settings.bridges.builtin_type = builtinType; + settings.bridges.bridge_strings = getBuiltinBridgeStrings(builtinType); + if (settings.bridges.bridge_strings.length == 0) { + // in this case the user is using a builtin bridge that is no longer supported, + // reset to settings to default values + settings.bridges.source = TorBridgeSource.Invalid; + settings.bridges.builtin_type = null; + } + } else { + settings.bridges.bridge_strings = []; + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + const bridgeString = Services.prefs.getStringPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + settings.bridges.bridge_strings.push(bridgeString); + }); + } + /* Proxy */ + settings.proxy.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.proxy.enabled); + if (settings.proxy.enabled) { + settings.proxy.type = Services.prefs.getIntPref(TorSettingsPrefs.proxy.type); + settings.proxy.address = Services.prefs.getStringPref(TorSettingsPrefs.proxy.address); + settings.proxy.port = Services.prefs.getIntPref(TorSettingsPrefs.proxy.port); + settings.proxy.username = Services.prefs.getStringPref(TorSettingsPrefs.proxy.username); + settings.proxy.password = Services.prefs.getStringPref(TorSettingsPrefs.proxy.password); + } else { + settings.proxy.type = TorProxyType.Invalid; + settings.proxy.address = null; + settings.proxy.port = 0; + settings.proxy.username = null; + settings.proxy.password = null; + } + + /* Firewall */ + settings.firewall.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.firewall.enabled); + if(settings.firewall.enabled) { + let portList = Services.prefs.getStringPref(TorSettingsPrefs.firewall.allowed_ports); + settings.firewall.allowed_ports = parsePortList(portList); + } else { + settings.firewall.allowed_ports = 0; + } + + this._settings = settings; + + return this; + }, + + // save our settings to prefs + saveToPrefs: function() { + console.log("TorSettings: saveToPrefs()"); + + let settings = this._settings; + + /* Quickstart */ + Services.prefs.setBoolPref(TorSettingsPrefs.quickstart.enabled, settings.quickstart.enabled); + /* Bridges */ + Services.prefs.setBoolPref(TorSettingsPrefs.bridges.enabled, settings.bridges.enabled); + Services.prefs.setIntPref(TorSettingsPrefs.bridges.source, settings.bridges.source); + Services.prefs.setStringPref(TorSettingsPrefs.bridges.builtin_type, settings.bridges.builtin_type); + // erase existing bridge strings + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + }); + // write new ones + if (settings.bridges.source !== TorBridgeSource.BuiltIn) { + settings.bridges.bridge_strings.forEach((string, index) => { + Services.prefs.setStringPref(`${TorSettingsPrefs.bridges.bridge_strings}.${index}`, string); + }); + } + /* Proxy */ + Services.prefs.setBoolPref(TorSettingsPrefs.proxy.enabled, settings.proxy.enabled); + if (settings.proxy.enabled) { + Services.prefs.setIntPref(TorSettingsPrefs.proxy.type, settings.proxy.type); + Services.prefs.setStringPref(TorSettingsPrefs.proxy.address, settings.proxy.address); + Services.prefs.setIntPref(TorSettingsPrefs.proxy.port, settings.proxy.port); + Services.prefs.setStringPref(TorSettingsPrefs.proxy.username, settings.proxy.username); + Services.prefs.setStringPref(TorSettingsPrefs.proxy.password, settings.proxy.password); + } else { + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password); + } + /* Firewall */ + Services.prefs.setBoolPref(TorSettingsPrefs.firewall.enabled, settings.firewall.enabled); + if (settings.firewall.enabled) { + Services.prefs.setStringPref(TorSettingsPrefs.firewall.allowed_ports, settings.firewall.allowed_ports.join(",")); + } else { + Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports); + } + + // all tor settings now stored in prefs :) + Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true); + + return this; + }, + + // push our settings down to the tor daemon + applySettings: async function() { + console.log("TorSettings: applySettings()"); + let settings = this._settings; + let settingsMap = new Map(); + + /* Bridges */ + const haveBridges = settings.bridges.enabled && settings.bridges.bridge_strings.length > 0; + settingsMap.set(TorConfigKeys.useBridges, haveBridges); + if (haveBridges) { + settingsMap.set(TorConfigKeys.bridgeList, settings.bridges.bridge_strings); + } else { + settingsMap.set(TorConfigKeys.bridgeList, null); + } + + /* Proxy */ + settingsMap.set(TorConfigKeys.socks4Proxy, null); + settingsMap.set(TorConfigKeys.socks5Proxy, null); + settingsMap.set(TorConfigKeys.socks5ProxyUsername, null); + settingsMap.set(TorConfigKeys.socks5ProxyPassword, null); + settingsMap.set(TorConfigKeys.httpsProxy, null); + settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null); + if (settings.proxy.enabled) { + let address = settings.proxy.address; + let port = settings.proxy.port; + let username = settings.proxy.username; + let password = settings.proxy.password; + + switch (settings.proxy.type) { + case TorProxyType.Socks4: + settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`); + break; + case TorProxyType.Socks5: + settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`); + settingsMap.set(TorConfigKeys.socks5ProxyUsername, username); + settingsMap.set(TorConfigKeys.socks5ProxyPassword, password); + break; + case TorProxyType.HTTPS: + settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`); + settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, `${username}:${password}`); + break; + } + } + + /* Firewall */ + if (settings.firewall.enabled) { + let reachableAddresses = settings.firewall.allowed_ports.map(port => `*:${port}`).join(","); + settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses); + } else { + settingsMap.set(TorConfigKeys.reachableAddresses, null); + } + + /* Push to Tor */ + await TorProtocolService.writeSettings(settingsMap); + + return this; + }, + + // set all of our settings at once from a settings object + setSettings: function(settings) { + console.log("TorSettings: setSettings()"); + let backup = this.getSettings(); + + try { + this._settings.bridges.enabled = !!settings.bridges.enabled; + this._settings.bridges.source = settings.bridges.source; + switch(settings.bridges.source) { + case TorBridgeSource.BridgeDB: + case TorBridgeSource.UserProvided: + this._settings.bridges.bridge_strings = settings.bridges.bridge_strings; + break; + case TorBridgeSource.BuiltIn: { + this._settings.bridges.builtin_type = settings.bridges.builtin_type; + settings.bridges.bridge_strings = getBuiltinBridgeStrings(settings.bridges.builtin_type); + if (settings.bridges.bridge_strings.length == 0 && settings.bridges.enabled) { + throw new Error(`No available builtin bridges of type ${settings.bridges.builtin_type}`); + } + this._settings.bridges.bridge_strings = settings.bridges.bridge_strings; + break; + } + case TorBridgeSource.Invalid: + break; + default: + if (settings.bridges.enabled) { + throw new Error(`Bridge source '${settings.source}' is not a valid source`); + } + break; + } + + // TODO: proxy and firewall + } catch(ex) { + this._settings = backup; + console.log(`TorSettings: setSettings failed => ${ex.message}`); + } + + console.log("TorSettings: setSettings result"); + console.log(this._settings); + }, + + // get a copy of all our settings + getSettings: function() { + console.log("TorSettings: getSettings()"); + // TODO: replace with structuredClone someday (post esr94): https://developer.mozilla.org/en-US/docs/Web/API/structuredClone + return JSON.parse(JSON.stringify(this._settings)); + }, + + /* Getters and Setters */ + + // Quickstart + get quickstart() { + return { + get enabled() { return self._settings.quickstart.enabled; }, + set enabled(val) { + if (val != self._settings.quickstart.enabled) + { + self._settings.quickstart.enabled = val; + Services.obs.notifyObservers({value: val}, TorSettingsTopics.SettingChanged, TorSettingsData.QuickStartEnabled); + } + }, + }; + }, + + // Bridges + get bridges() { + return { + get enabled() { return self._settings.bridges.enabled; }, + set enabled(val) { + self._settings.bridges.enabled = val; + }, + get source() { return self._settings.bridges.source; }, + set source(val) { self._settings.bridges.source = val; }, + get builtin_type() { return self._settings.bridges.builtin_type; }, + set builtin_type(val) { + const bridgeStrings = getBuiltinBridgeStrings(val); + if (bridgeStrings.length > 0) { + self._settings.bridges.builtin_type = val; + self._settings.bridges.bridge_strings = bridgeStrings; + } else { + self._settings.bridges.builtin_type = ""; + if (self._settings.bridges.source === TorBridgeSource.BuiltIn) { + self._settings.bridges.source = TorBridgeSource.Invalid; + } + } + }, + get bridge_strings() { return arrayCopy(self._settings.bridges.bridge_strings); }, + set bridge_strings(val) { + self._settings.bridges.bridge_strings = parseBridgeStrings(val); + }, + }; + }, + + // Proxy + get proxy() { + return { + get enabled() { return self._settings.proxy.enabled; }, + set enabled(val) { + self._settings.proxy.enabled = val; + // reset proxy settings + self._settings.proxy.type = TorProxyType.Invalid; + self._settings.proxy.address = null; + self._settings.proxy.port = 0; + self._settings.proxy.username = null; + self._settings.proxy.password = null; + }, + get type() { return self._settings.proxy.type; }, + set type(val) { self._settings.proxy.type = val; }, + get address() { return self._settings.proxy.address; }, + set address(val) { self._settings.proxy.address = val; }, + get port() { return arrayCopy(self._settings.proxy.port); }, + set port(val) { self._settings.proxy.port = parsePort(val); }, + get username() { return self._settings.proxy.username; }, + set username(val) { self._settings.proxy.username = val; }, + get password() { return self._settings.proxy.password; }, + set password(val) { self._settings.proxy.password = val; }, + get uri() { + switch (this.type) { + case TorProxyType.Socks4: + return `socks4a://${this.address}:${this.port}`; + case TorProxyType.Socks5: + if (this.username) { + return `socks5://${this.username}:${this.password}@${this.address}:${this.port}`; + } + return `socks5://${this.address}:${this.port}`; + case TorProxyType.HTTPS: + if (this._proxyUsername) { + return `http://$%7Bthis.username%7D:$%7Bthis.password%7D@$%7Bthis.address%7D:$%7Bthi...; + } + return `http://$%7Bthis.address%7D:$%7Bthis.port%7D%60; + } + return null; + }, + }; + }, + + // Firewall + get firewall() { + return { + get enabled() { return self._settings.firewall.enabled; }, + set enabled(val) { + self._settings.firewall.enabled = val; + // reset firewall settings + self._settings.firewall.allowed_ports = []; + }, + get allowed_ports() { return self._settings.firewall.allowed_ports; }, + set allowed_ports(val) { self._settings.firewall.allowed_ports = parsePortList(val); }, + }; + }, + }; + self.init(); + return self; +})(); diff --git a/browser/modules/moz.build b/browser/modules/moz.build index b069f1b641f70..646784690c9a6 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -128,6 +128,7 @@ EXTRA_JS_MODULES += [ "AboutNewTab.jsm", "AppUpdater.jsm", "AsyncTabSwitcher.jsm", + "BridgeDB.jsm", "BrowserUIUtils.jsm", "BrowserUsageTelemetry.jsm", "BrowserWindowTracker.jsm", @@ -138,6 +139,7 @@ EXTRA_JS_MODULES += [ "FaviconLoader.jsm", "HomePage.jsm", "LaterRun.jsm", + 'Moat.jsm', "NewTabPagePreloading.jsm", "OpenInTabsUtils.jsm", "PageActions.jsm", @@ -152,6 +154,8 @@ EXTRA_JS_MODULES += [ "TabsList.jsm", "TabUnloader.jsm", "ThemeVariableMap.jsm", + "TorProtocolService.jsm", + "TorSettings.jsm", "TransientPrefs.jsm", "webrtcUI.jsm", "ZoomUI.jsm", diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm index ecdbf2a01d99a..7bde782e54ce8 100644 --- a/toolkit/components/processsingleton/MainProcessSingleton.jsm +++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm @@ -24,6 +24,11 @@ MainProcessSingleton.prototype = { null );
+ ChromeUtils.import( + "resource:///modules/TorSettings.jsm", + null + ); + Services.ppmm.loadProcessScript( "chrome://global/content/process-content.js", true
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 91ad24bac8bf814874739e655819e80b5d1ae531 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Fri Apr 8 15:24:50 2022 +0200
fixup! Bug 40597: Implement TorSettings module
Changes introduced by !275 --- browser/modules/Moat.jsm | 37 +++++++- browser/modules/TorConnect.jsm | 210 +++++++++++++++++++++++++++++++++-------- 2 files changed, 206 insertions(+), 41 deletions(-)
diff --git a/browser/modules/Moat.jsm b/browser/modules/Moat.jsm index 2995a9148f0a3..35931172f3ee4 100644 --- a/browser/modules/Moat.jsm +++ b/browser/modules/Moat.jsm @@ -393,7 +393,7 @@ class InternetTestResponseListener { }
// callers wait on this for final response - status() { + get status() { return this._promise; }
@@ -406,8 +406,18 @@ class InternetTestResponseListener { statuses = { components: status, successful: Components.isSuccessCode(status), - http: request.responseStatus, }; + try { + if (statuses.successful) { + statuses.http = request.responseStatus; + statuses.date = request.getResponseHeader("Date"); + } + } catch (err) { + console.warn( + "Successful request, but could not get the HTTP status or date", + err + ); + } } catch (err) { this._reject(err); } @@ -549,7 +559,7 @@ class MoatRPC {
const listener = new InternetTestResponseListener(); await ch.asyncOpen(listener, ch); - return listener.status(); + return listener.status; }
// @@ -777,4 +787,25 @@ class MoatRPC {
return map; } + + // Request a copy of the defaul/fallback bridge settings, takes the following parameters: + // - transports: optional, an array of transports available to the client; if empty (or not + // given) returns settings using all working transports known to the server + // + // returns an array of settings objects in roughly the same format as the _settings + // object on the TorSettings module + async circumvention_defaults(transports) { + const args = { + transports: transports ? transports : [], + }; + const response = await this._makeRequest("circumvention/defaults", args); + if ("errors" in response) { + const code = response.errors[0].code; + const detail = response.errors[0].detail; + throw new Error(`MoatRPC: ${detail} (${code})`); + } else if ("settings" in response) { + return this._fixupSettingsList(response.settings); + } + return []; + } } diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm index 13c1f54d2ee97..711134326a14d 100644 --- a/browser/modules/TorConnect.jsm +++ b/browser/modules/TorConnect.jsm @@ -1,12 +1,12 @@ "use strict";
-var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState", "TorCensorshipLevel"]; +var EXPORTED_SYMBOLS = ["InternetStatus", "TorConnect", "TorConnectTopics", "TorConnectState"];
const { Services } = ChromeUtils.import( "resource://gre/modules/Services.jsm" );
-const { setTimeout } = ChromeUtils.import( +const { setTimeout, clearTimeout } = ChromeUtils.import( "resource://gre/modules/Timer.jsm" );
@@ -61,17 +61,6 @@ const TorConnectState = Object.freeze({ Disabled: "Disabled", });
-const TorCensorshipLevel = Object.freeze({ - /* No censorship detected */ - None: 0, - /* Moderate censorship detected, autobootstrap may evade it */ - Moderate: 1, - /* Severe censorship detected, but connection may still succeed */ - Severe: 2, - /* Extreme censorship detected, connection will always end in an error */ - Extreme: 3, -}); - /* TorConnect State Transitions
@@ -225,13 +214,98 @@ const debug_sleep = async (ms) => { }); }
+const InternetStatus = Object.freeze({ + Unknown: -1, + Offline: 0, + Online: 1, +}); + +class InternetTest { + constructor() { + this._status = InternetStatus.Unknown; + this._error = null; + this._pending = false; + this._timeout = setTimeout(() => { + this._timeout = null; + this.test(); + }, this.timeoutRand()); + this.onResult = (online, date) => {} + this.onError = (err) => {}; + } + + test() { + if (this._pending) { + return; + } + this.cancel(); + this._pending = true; + + this._testAsync() + .then((status) => { + this._pending = false; + this._status = status.successful ? InternetStatus.Online : InternetStatus.Offline; + this.onResult(this.status, status.date); + }) + .catch(error => { + this._error = error; + this._pending = false; + this.onError(error); + }); + } + + cancel() { + if (this._timeout !== null) { + clearTimeout(this._timeout); + this._timeout = null; + } + } + + async _testAsync() { + // Callbacks for the Internet test are desirable, because we will be + // waiting both for the bootstrap, and for the Internet test. + // However, managing Moat with async/await is much easier as it avoids a + // callback hell, and it makes extra explicit that we are uniniting it. + const mrpc = new MoatRPC(); + let status = null; + let error = null; + try { + await mrpc.init(); + status = await mrpc.testInternetConnection(); + } catch (err) { + console.error("Error while checking the Internet connection", err); + error = err; + } finally { + mrpc.uninit(); + } + if (error !== null) { + throw error; + } + return status; + } + + get status() { + return this._status; + } + + get error() { + return this._error; + } + + // We randomize the Internet test timeout to make fingerprinting it harder, at least a little bit... + timeoutRand() { + const offset = 30000; + const randRange = 5000; + return offset + randRange * (Math.random() * 2 - 1); + } +} + const TorConnect = (() => { let retval = {
_state: TorConnectState.Initial, _bootstrapProgress: 0, _bootstrapStatus: null, - _detectedCensorshiplevel: TorCensorshipLevel.None, + _internetStatus: InternetStatus.Unknown, // list of country codes Moat has settings for _countryCodes: [], _countryNames: Object.freeze((() => { @@ -246,8 +320,13 @@ const TorConnect = (() => { _errorMessage: null, _errorDetails: null, _logHasWarningOrError: false, + _hasBootstrapEverFailed: false, _transitionPromise: null,
+ // This is used as a helper to make the state of about:torconnect persistent + // during a session, but TorConnect does not use this data at all. + _uiState: {}, + /* These functions represent ongoing work associated with one of our states Some of these functions are mostly empty, apart from defining an on_transition function used to resolve their Promise */ @@ -294,26 +373,49 @@ const TorConnect = (() => { [TorConnectState.Bootstrapping, new StateCallback(TorConnectState.Bootstrapping, async function() { // wait until bootstrap completes or we get an error await new Promise(async (resolve, reject) => { - - // we reset the bootstrap failure count so users can manually try settings without the - // censorship circumvention state machine getting in the way - TorConnect._detectedCensorshipLevel = TorCensorshipLevel.None; - // debug hook to simulate censorship preventing bootstrapping if (Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) > 0) { this.on_transition = (nextState) => { resolve(); }; await debug_sleep(1500); + TorConnect._hasBootstrapEverFailed = true; TorConnect._changeState(TorConnectState.Error, "Bootstrap failed (for debugging purposes)", "Error: Censorship simulation", true); TorProtocolService._torBootstrapDebugSetError(); return; }
const tbr = new TorBootstrapRequest(); + const internetTest = new InternetTest(); + + let bootstrapError = ""; + let bootstrapErrorDetails = ""; + const maybeTransitionToError = () => { + if (internetTest.status === InternetStatus.Unknown && internetTest.error === null) { + // We have been called by a failed bootstrap, but the internet test has not run yet - force + // it to run immediately! + internetTest.test(); + // Return from this call, because the Internet test's callback will call us again + return; + } + // Do not transition to the offline error until we are sure that also the bootstrap failed, in + // case Moat is down but the bootstrap can proceed anyway. + if (bootstrapError === "") { + return; + } + if (internetTest.status === InternetStatus.Offline) { + TorConnect._changeState(TorConnectState.Error, TorStrings.torConnect.offline, "", true); + } else { + // Give priority to the bootstrap error, in case the Internet test fails + TorConnect._hasBootstrapEverFailed = true; + TorConnect._changeState(TorConnectState.Error, bootstrapError, bootstrapErrorDetails, true); + } + } + this.on_transition = async (nextState) => { if (nextState === TorConnectState.Configuring) { // stop bootstrap process if user cancelled + internetTest.cancel(); await tbr.cancel(); } resolve(); @@ -323,11 +425,24 @@ const TorConnect = (() => { TorConnect._updateBootstrapStatus(progress, status); }; tbr.onbootstrapcomplete = () => { + internetTest.cancel(); TorConnect._changeState(TorConnectState.Bootstrapped); }; tbr.onbootstraperror = (message, details) => { - TorConnect._changeState(TorConnectState.Error, message, details, true); + // We have to wait for the Internet test to finish before sending the bootstrap error + bootstrapError = message; + bootstrapErrorDetails = details; + maybeTransitionToError(); + }; + + internetTest.onResult = (status, date) => { + // TODO: Use the date to save the clock skew? + TorConnect._internetStatus = status; + maybeTransitionToError(); }; + internetTest.onError = () => { + maybeTransitionToError(); + }
tbr.bootstrap(); }); @@ -349,7 +464,7 @@ const TorConnect = (() => { // always fail even after manually selecting location specific settings if (censorshipLevel == 3) { await debug_sleep(2500); - TorConnect._changeState(TorConnectState.Error, "Error: Extreme Censorship simulation", "", true); + TorConnect._changeState(TorConnectState.Error, "Error: censorship simulation", "", true); return; // only fail after auto selecting, manually selecting succeeds } else if (censorshipLevel == 2 && !countryCode) { @@ -378,12 +493,24 @@ const TorConnect = (() => {
if (this.transitioning) return;
- if (this.settings === null) { - // unable to determine country - throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry); - } else if (this.settings.length === 0) { - // no settings available for country - throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry); + const noCountry = this.settings !== null; + const noLocalizedSettings = this.settings && this.settings.length === 0; + if (noCountry || noLocalizedSettings) { + try { + this.settings = await this.mrpc.circumvention_defaults([...TorBuiltinBridgeTypes, "vanilla"]); + } catch (err) { + console.error("We could not get localized settings, but defaults settings failed as well", err); + } + } + if (this.settings === null || this.settings.length === 0) { + // The fallback has failed as well, so throw the original error + if (noCountry) { + // unable to determine country + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry); + } else { + // no settings available for country + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry); + } }
// apply each of our settings and try to bootstrap with each @@ -477,11 +604,7 @@ const TorConnect = (() => { TorConnect._errorMessage = errorMessage; TorConnect._errorDetails = errorDetails;
- if (bootstrappingFailure && TorConnect._detectedCensorshipLevel < TorCensorshipLevel.Extreme) { - TorConnect._detectedCensorshipLevel += 1; - } - - Services.obs.notifyObservers({message: errorMessage, details: errorDetails, censorshipLevel: TorConnect.detectedCensorshipLevel}, TorConnectTopics.BootstrapError); + Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError);
TorConnect._changeState(TorConnectState.Configuring); }); @@ -615,8 +738,16 @@ const TorConnect = (() => { return this._bootstrapStatus; },
- get detectedCensorshipLevel() { - return this._detectedCensorshipLevel; + get internetStatus() { + return this._internetStatus; + }, + + get countryCodes() { + return this._countryCodes; + }, + + get countryNames() { + return this._countryNames; },
get errorMessage() { @@ -631,12 +762,15 @@ const TorConnect = (() => { return this._logHasWarningOrError; },
- get countryCodes() { - return this._countryCodes; + get hasBootstrapEverFailed() { + return this._hasBootstrapEverFailed; },
- get countryNames() { - return this._countryNames; + get uiState() { + return this._uiState; + }, + set uiState(newState) { + this._uiState = newState; },
/*
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit a001d0e89b94f5aaeb72b33c0dd9e2e76bcbbd8b Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Tue May 17 16:38:11 2022 +0200
fixup! Bug 40597: Implement TorSettings module
Fixes for bugs found in 91.9, to keep as a fixup also in 91.10.
Bug 40923: Make available the country code detected by Moat --- browser/modules/Moat.jsm | 9 ++++++--- browser/modules/TorConnect.jsm | 24 ++++++++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/browser/modules/Moat.jsm b/browser/modules/Moat.jsm index 35931172f3ee4..90a6ae4e521cc 100644 --- a/browser/modules/Moat.jsm +++ b/browser/modules/Moat.jsm @@ -735,6 +735,7 @@ class MoatRPC { country, }; const response = await this._makeRequest("circumvention/settings", args); + let settings = {}; if ("errors" in response) { const code = response.errors[0].code; const detail = response.errors[0].detail; @@ -748,10 +749,12 @@ class MoatRPC {
throw new Error(`MoatRPC: ${detail} (${code})`); } else if ("settings" in response) { - return this._fixupSettingsList(response.settings); + settings.settings = this._fixupSettingsList(response.settings); } - - return []; + if ("country" in response) { + settings.country = response.country; + } + return settings; }
// Request a list of country codes with available censorship circumvention settings diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm index 711134326a14d..0edbb7d70c47f 100644 --- a/browser/modules/TorConnect.jsm +++ b/browser/modules/TorConnect.jsm @@ -317,6 +317,7 @@ const TorConnect = (() => { } return codesNames; })()), + _detectedLocation: "", _errorMessage: null, _errorDetails: null, _logHasWarningOrError: false, @@ -380,6 +381,10 @@ const TorConnect = (() => { }; await debug_sleep(1500); TorConnect._hasBootstrapEverFailed = true; + if (Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) === 2) { + const codes = Object.keys(TorConnect._countryNames); + TorConnect._detectedLocation = codes[Math.floor(Math.random() * codes.length)]; + } TorConnect._changeState(TorConnectState.Error, "Bootstrap failed (for debugging purposes)", "Error: Censorship simulation", true); TorProtocolService._torBootstrapDebugSetError(); return; @@ -489,22 +494,25 @@ const TorConnect = (() => {
if (this.transitioning) return;
- this.settings = await this.mrpc.circumvention_settings([...TorBuiltinBridgeTypes, "vanilla"], countryCode); + const settings = await this.mrpc.circumvention_settings([...TorBuiltinBridgeTypes, "vanilla"], countryCode);
if (this.transitioning) return;
- const noCountry = this.settings !== null; - const noLocalizedSettings = this.settings && this.settings.length === 0; - if (noCountry || noLocalizedSettings) { + if (settings?.country) { + TorConnect._detectedLocation = settings.country; + } + if (settings?.settings && settings.settings.length > 0) { + this.settings = settings.settings; + } else { try { this.settings = await this.mrpc.circumvention_defaults([...TorBuiltinBridgeTypes, "vanilla"]); } catch (err) { - console.error("We could not get localized settings, but defaults settings failed as well", err); + console.error("We did not get localized settings, and default settings failed as well", err); } } if (this.settings === null || this.settings.length === 0) { // The fallback has failed as well, so throw the original error - if (noCountry) { + if (!TorConnect._detectedLocation) { // unable to determine country throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry); } else { @@ -750,6 +758,10 @@ const TorConnect = (() => { return this._countryNames; },
+ get detectedLocation() { + return this._detectedLocation; + }, + get errorMessage() { return this._errorMessage; },
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit e857e60c1f8574da1122feed3325393bb775b582 Author: Alex Catarineu acat@torproject.org AuthorDate: Wed Feb 19 23:05:08 2020 +0100
Bug 10760: Integrate TorButton to TorBrowser core
Because of the non-restartless nature of Torbutton, it required a two-stage installation process. On mobile, it was a problem, because it was not loading when the user opened the browser for the first time.
Moving it to tor-browser and making it a system extension allows it to load when the user opens the browser for first time.
Additionally, this patch also fixes Bug 27611.
Bug 26321: New Circuit and New Identity menu items
Bug 14392: Make about:tor behave like other initial pages.
Bug 25013: Add torbutton as a tor-browser submodule
Bug 31575: Replace Firefox Home (newtab) with about:tor
Avoid loading AboutNewTab in BrowserGlue.jsm in order to avoid several network requests that we do not need. Besides, about:newtab will now point to about:blank or about:tor (depending on browser.newtabpage.enabled) and about:home will point to about:tor. --- .gitmodules | 3 ++ browser/base/content/aboutDialog.xhtml | 38 +++++++++++------- browser/base/content/appmenu-viewcache.inc.xhtml | 28 +++++++++++++- browser/base/content/browser-doctype.inc | 6 +++ browser/base/content/browser-menubar.inc | 45 ++++++++++++++++------ browser/base/content/browser-sets.inc | 2 + browser/base/content/browser.js | 1 + browser/base/content/browser.xhtml | 9 +++++ browser/components/BrowserGlue.jsm | 33 +--------------- .../controlcenter/content/identityPanel.inc.xhtml | 22 +++++++++++ browser/components/newtab/AboutNewTabService.jsm | 15 +------- browser/components/preferences/home.inc.xhtml | 4 +- browser/components/preferences/preferences.xhtml | 5 ++- browser/installer/package-manifest.in | 2 + browser/modules/HomePage.jsm | 2 +- docshell/base/nsAboutRedirector.cpp | 6 ++- docshell/build/components.conf | 1 + mobile/android/installer/package-manifest.in | 4 ++ toolkit/moz.build | 1 + .../mozapps/extensions/internal/XPIProvider.jsm | 9 +++++ toolkit/torproject/torbutton | 1 + .../lib/environments/browser-window.js | 6 ++- 22 files changed, 166 insertions(+), 77 deletions(-)
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..2f03bd8e22df9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "toolkit/torproject/torbutton"] + path = toolkit/torproject/torbutton + url = https://git.torproject.org/torbutton.git diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml index 55c8b1c2c5f79..4eb122b0b2d8d 100644 --- a/browser/base/content/aboutDialog.xhtml +++ b/browser/base/content/aboutDialog.xhtml @@ -7,11 +7,11 @@ <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/content/aboutDialog.css" type="text/css"?> <?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?> +<?xml-stylesheet href="chrome://torbutton/skin/aboutDialog.css" type="text/css"?>
+<!-- We need to include the localization DTDs until we migrate to Fluent --> <!DOCTYPE window [ -#ifdef XP_MACOSX #include browser-doctype.inc -#endif ]>
<window xmlns:html="http://www.w3.org/1999/xhtml" @@ -28,7 +28,7 @@ data-l10n-id="aboutDialog-title" #endif role="dialog" - aria-describedby="version distribution distributionId communityDesc contributeDesc trademark" + aria-describedby="version distribution distributionId projectDesc helpDesc trademark trademarkTor" > #ifdef XP_MACOSX #include macWindow.inc.xhtml @@ -146,24 +146,36 @@ <label is="text-link" useoriginprincipal="true" href="about:credits" data-l10n-name="community-exp-creditsLink"/> </description> </vbox> - <description class="text-blurb" id="communityDesc" data-l10n-id="community-2"> - <label is="text-link" href="https://www.mozilla.org/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-name="community-mozillaLink"/> - <label is="text-link" useoriginprincipal="true" href="about:credits" data-l10n-name="community-creditsLink"/> + <!-- Keep communityDesc and contributeDesc to avoid JS errors trying to hide them --> + <description class="text-blurb" id="communityDesc" data-l10n-id="community-2" hidden="true"></description> + <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus" hidden="true"></description> + <description class="text-blurb" id="projectDesc"> + &project.start; + <label is="text-link" href="https://www.torproject.org/"> + &project.tpoLink; + </label>&project.end; </description> - <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus"> - <label is="text-link" href="https://donate.mozilla.org/?utm_source=firefox&utm_medium=referral&utm_campaign=firefox_about&utm_content=firefox_about" data-l10n-name="helpus-donateLink"/> - <label is="text-link" href="https://www.mozilla.org/contribute/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-name="helpus-getInvolvedLink"/> + <description class="text-blurb" id="helpDesc"> + &help.start; + <label is="text-link" href="https://donate.torproject.org/"> + &help.donateLink; + </label> + &help.or; + <label is="text-link" href="https://community.torproject.org/"> + &help.getInvolvedLink; + </label>&help.end; </description> </vbox> </vbox> </hbox> <vbox id="bottomBox"> - <hbox pack="center"> - <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license" data-l10n-id="bottomLinks-license"/> - <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:rights" data-l10n-id="bottomLinks-rights"/> - <label is="text-link" class="bottom-link" href="https://www.mozilla.org/privacy/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-id="bottomLinks-privacy"/> + <hbox id="newBottom" pack="center" position="1"> + <label is="text-link" class="bottom-link" href="https://support.torproject.org/">&bottomLinks.questions;</label> + <label is="text-link" class="bottom-link" href="https://community.torproject.org/relay/">&bottomLinks.grow;</label> + <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label> </hbox> <description id="trademark" data-l10n-id="trademarkInfo"></description> + <description id="trademarkTor">&tor.TrademarkStatement;</description> </vbox> </vbox>
diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml index 895ef976fc23c..a473509f1647c 100644 --- a/browser/base/content/appmenu-viewcache.inc.xhtml +++ b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -45,7 +45,8 @@ class="subviewbutton subviewbutton-iconic" data-l10n-id="appmenuitem-new-private-window" key="key_privatebrowsing" - command="Tools:PrivateBrowsing"/> + command="Tools:PrivateBrowsing" + hidden="true"/> #ifdef NIGHTLY_BUILD <toolbarbutton id="appMenu-fission-window-button" class="subviewbutton subviewbutton-iconic" @@ -61,7 +62,19 @@ <toolbarbutton id="appMenuRestoreLastSession" data-l10n-id="appmenu-restore-session" class="subviewbutton subviewbutton-iconic" - command="Browser:RestoreLastSession"/> + command="Browser:RestoreLastSession" + hidden="true"/> + <toolbarseparator/> + <toolbarbutton id="appMenuNewIdentity" + class="subviewbutton subviewbutton-iconic" + key="torbutton-new-identity-key" + label="&torbutton.context_menu.new_identity;" + oncommand="torbutton_new_identity();"/> + <toolbarbutton id="appMenuNewCircuit" + class="subviewbutton subviewbutton-iconic" + key="torbutton-new-circuit-key" + label="&torbutton.context_menu.new_circuit;" + oncommand="torbutton_new_circuit();"/> <toolbarseparator/> <toolbaritem id="appMenu-zoom-controls" class="toolbaritem-combined-buttons" closemenu="none"> <!-- Use a spacer, because panel sizing code gets confused when using CSS methods. --> @@ -256,6 +269,17 @@ key="key_privatebrowsing" command="Tools:PrivateBrowsing"/> <toolbarseparator/> + <toolbarbutton id="appMenuNewIdentity" + class="subviewbutton" + key="torbutton-new-identity-key" + label="&torbutton.context_menu.new_identity_sentence_case;" + oncommand="torbutton_new_identity();"/> + <toolbarbutton id="appMenuNewCircuit" + class="subviewbutton" + key="torbutton-new-circuit-key" + label="&torbutton.context_menu.new_circuit_sentence_case;" + oncommand="torbutton_new_circuit();"/> + <toolbarseparator/> <toolbarbutton id="appMenu-bookmarks-button" class="subviewbutton subviewbutton-nav" data-l10n-id="library-bookmarks-menu" diff --git a/browser/base/content/browser-doctype.inc b/browser/base/content/browser-doctype.inc index cea0382acde26..691d16a7b2e5e 100644 --- a/browser/base/content/browser-doctype.inc +++ b/browser/base/content/browser-doctype.inc @@ -6,3 +6,9 @@ %textcontextDTD; <!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd"> %placesDTD; +<!ENTITY % torbuttonDTD SYSTEM "chrome://torbutton/locale/torbutton.dtd"> +%torbuttonDTD; +<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd"> +%aboutTorDTD; +<!ENTITY % aboutDialogDTD SYSTEM "chrome://torbutton/locale/aboutDialog.dtd"> +%aboutDialogDTD; diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index cd348e8e78176..4b7564cea0878 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -38,6 +38,18 @@ command="Tools:NonFissionWindow" accesskey="s" label="New Non-Fission Window"/> #endif + <menuseparator/> + <menuitem id="menu_newIdentity" + accesskey="&torbutton.context_menu.new_identity_key;" + key="torbutton-new-identity-key" + label="&torbutton.context_menu.new_identity;" + oncommand="torbutton_new_identity();"/> + <menuitem id="menu_newCircuit" + accesskey="&torbutton.context_menu.new_circuit_key;" + key="torbutton-new-circuit-key" + label="&torbutton.context_menu.new_circuit;" + oncommand="torbutton_new_circuit();"/> + <menuseparator/> <menuitem id="menu_openLocation" hidden="true" command="Browser:OpenLocation" @@ -463,23 +475,34 @@ <menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();"> <!-- Note: Items under here are cloned to the AppMenu Help submenu. The cloned items have their strings defined by appmenu-data-l10n-id. --> - <menuitem id="menu_openHelp" + <!-- dummy elements to avoid 'getElementById' errors --> + <box id="feedbackPage"/> + <box id="helpSafeMode"/> + <box id="menu_HelpPopup_reportPhishingtoolmenu"/> + <box id="menu_HelpPopup_reportPhishingErrortoolmenu"/> + <!-- Add Tor Browser manual link --> + <menuitem id="torBrowserUserManual" + oncommand="gBrowser.selectedTab = gBrowser.addTab('https://tb-manual.torproject.org/' + Services.locale.requestedLocale, {triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});" + label="&aboutTor.torbrowser_user_manual.label;" + accesskey="&aboutTor.torbrowser_user_manual.accesskey;"/> + <!-- Bug 18905: Hide unused help menu items --> + <!-- <menuitem id="menu_openHelp" oncommand="openHelpLink('firefox-help')" data-l10n-id="menu-get-help" appmenu-data-l10n-id="appmenu-get-help" #ifdef XP_MACOSX - key="key_openHelpMac"/> + key="key_openHelpMac"/> --> #else - /> + /> --> #endif - <menuitem id="feedbackPage" + <!-- <menuitem id="feedbackPage" oncommand="openFeedbackPage()" data-l10n-id="menu-help-feedback-page" - appmenu-data-l10n-id="appmenu-help-feedback-page"/> - <menuitem id="helpSafeMode" + appmenu-data-l10n-id="appmenu-help-feedback-page"/> --> + <!-- <menuitem id="helpSafeMode" oncommand="safeModeRestart();" data-l10n-id="menu-help-enter-troubleshoot-mode2" - appmenu-data-l10n-id="appmenu-help-enter-troubleshoot-mode2"/> + appmenu-data-l10n-id="appmenu-help-enter-troubleshoot-mode2"/> --> <menuitem id="troubleShooting" oncommand="openTroubleshootingPage()" data-l10n-id="menu-help-more-troubleshooting-info" @@ -489,18 +512,18 @@ data-l10n-id="menu-help-report-site-issue" appmenu-data-l10n-id="appmenu-help-report-site-issue" hidden="true"/> - <menuitem id="menu_HelpPopup_reportPhishingtoolmenu" + <!-- <menuitem id="menu_HelpPopup_reportPhishingtoolmenu" disabled="true" oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event, {triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})});" hidden="true" data-l10n-id="menu-help-report-deceptive-site" - appmenu-data-l10n-id="appmenu-help-report-deceptive-site"/> - <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu" + appmenu-data-l10n-id="appmenu-help-report-deceptive-site"/> --> + <!-- <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu" disabled="true" oncommand="ReportFalseDeceptiveSite();" data-l10n-id="menu-help-not-deceptive" appmenu-data-l10n-id="appmenu-help-not-deceptive" - hidden="true"/> + hidden="true"/> --> <menuseparator id="aboutSeparator"/> <menuitem id="aboutName" oncommand="openAboutDialog();" diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index fdd83f64896e0..c3129d6aae077 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -383,4 +383,6 @@ data-l10n-id="hide-other-apps-shortcut" modifiers="accel,alt"/> #endif + <key id="torbutton-new-identity-key" modifiers="accel shift" key="U" oncommand="torbutton_new_identity()"/> + <key id="torbutton-new-circuit-key" modifiers="accel shift" key="L" oncommand="torbutton_new_circuit()"/> </keyset> diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index dab26aeeb1793..566976b6d7aa0 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -632,6 +632,7 @@ var gPageIcons = { };
var gInitialPages = [ + "about:tor", "about:blank", "about:newtab", "about:home", diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 82fd0d32d670d..8efb544918b8e 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -29,6 +29,8 @@ <?xml-stylesheet href="chrome://browser/skin/searchbar.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/skin/places/tree-icons.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css" type="text/css"?> +<?xml-stylesheet href="chrome://torbutton/skin/tor-circuit-display.css" type="text/css"?> +<?xml-stylesheet href="chrome://torbutton/skin/torbutton.css" type="text/css"?>
# All DTD information is stored in a separate file so that it can be shared by # hiddenWindowMac.xhtml. @@ -106,11 +108,18 @@ Services.scriptloader.loadSubScript("chrome://browser/content/places/places-menupopup.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/search/autocomplete-popup.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this); + Services.scriptloader.loadSubScript("chrome://torbutton/content/tor-circuit-display.js", this); + Services.scriptloader.loadSubScript("chrome://torbutton/content/torbutton.js", this);
window.onload = gBrowserInit.onLoad.bind(gBrowserInit); window.onunload = gBrowserInit.onUnload.bind(gBrowserInit); window.onclose = WindowIsClosing;
+ //onLoad Handler + try { + window.addEventListener("load", torbutton_init, false); + } catch (e) {} + window.addEventListener("MozBeforeInitialXULLayout", gBrowserInit.onBeforeInitialXULLayout.bind(gBrowserInit), { once: true });
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 2170fe472a952..58db8ff37ce90 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -20,7 +20,6 @@ const { AppConstants } = ChromeUtils.import( Cu.importGlobalProperties(["Glean"]);
XPCOMUtils.defineLazyModuleGetters(this, { - AboutNewTab: "resource:///modules/AboutNewTab.jsm", ActorManagerParent: "resource://gre/modules/ActorManagerParent.jsm", AddonManager: "resource://gre/modules/AddonManager.jsm", AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm", @@ -211,28 +210,6 @@ let JSWINDOWACTORS = { matches: ["about:logins", "about:logins?*", "about:loginsimportreport"], },
- AboutNewTab: { - parent: { - moduleURI: "resource:///actors/AboutNewTabParent.jsm", - }, - child: { - moduleURI: "resource:///actors/AboutNewTabChild.jsm", - events: { - DOMContentLoaded: {}, - pageshow: {}, - visibilitychange: {}, - }, - }, - // The wildcard on about:newtab is for the ?endpoint query parameter - // that is used for snippets debugging. The wildcard for about:home - // is similar, and also allows for falling back to loading the - // about:home document dynamically if an attempt is made to load - // about:home?jscache from the AboutHomeStartupCache as a top-level - // load. - matches: ["about:home*", "about:welcome", "about:newtab*"], - remoteTypes: ["privilegedabout"], - }, - AboutPlugins: { parent: { moduleURI: "resource:///actors/AboutPluginsParent.jsm", @@ -1617,8 +1594,6 @@ BrowserGlue.prototype = {
// the first browser window has finished initializing _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) { - AboutNewTab.init(); - TabCrashHandler.init();
ProcessHangMonitor.init(); @@ -5319,12 +5294,8 @@ var AboutHomeStartupCache = { return { pageInputStream: null, scriptInputStream: null }; }
- let state = AboutNewTab.activityStream.store.getState(); - return new Promise(resolve => { - this._cacheDeferred = resolve; - this.log.trace("Parent is requesting cache streams."); - this._procManager.sendAsyncMessage(this.CACHE_REQUEST_MESSAGE, { state }); - }); + this.log.error("Activity Stream is disabled in Tor Browser."); + return { pageInputStream: null, scriptInputStream: null }; },
/** diff --git a/browser/components/controlcenter/content/identityPanel.inc.xhtml b/browser/components/controlcenter/content/identityPanel.inc.xhtml index 9a41ac1f33ccd..06511866ae293 100644 --- a/browser/components/controlcenter/content/identityPanel.inc.xhtml +++ b/browser/components/controlcenter/content/identityPanel.inc.xhtml @@ -92,6 +92,28 @@ </vbox> </hbox>
+ <!-- Circuit display section --> + + <vbox id="circuit-display-container" class="identity-popup-section"> + <toolbarseparator/> + <vbox id="circuit-display-header" flex="1" role="group" + aria-labelledby="circuit-display-headline"> + <hbox flex="1"> + <label id="circuit-display-headline" + role="heading" aria-level="2">&torbutton.circuit_display.title;</label> + </hbox> + </vbox> + <vbox id="circuit-display-content"> + <html:ul id="circuit-display-nodes" dir="auto"/> + <hbox id="circuit-guard-note-container"/> + <hbox id="circuit-reload-button-container"> + <html:button id="circuit-reload-button" + onclick="torbutton_new_circuit()" + default="true">&torbutton.circuit_display.new_circuit;</html:button> + </hbox> + </vbox> + </vbox> + <!-- Clear Site Data Button --> <vbox hidden="true" id="identity-popup-clear-sitedata-footer"> diff --git a/browser/components/newtab/AboutNewTabService.jsm b/browser/components/newtab/AboutNewTabService.jsm index 44308daa2b2d8..d98c014e3f9e6 100644 --- a/browser/components/newtab/AboutNewTabService.jsm +++ b/browser/components/newtab/AboutNewTabService.jsm @@ -420,20 +420,7 @@ class BaseAboutNewTabService { * the newtab page has no effect on the result of this function. */ get defaultURL() { - // Generate the desired activity stream resource depending on state, e.g., - // "resource://activity-stream/prerendered/activity-stream.html" - // "resource://activity-stream/prerendered/activity-stream-debug.html" - // "resource://activity-stream/prerendered/activity-stream-noscripts.html" - return [ - "resource://activity-stream/prerendered/", - "activity-stream", - // Debug version loads dev scripts but noscripts separately loads scripts - this.activityStreamDebug && !this.privilegedAboutProcessEnabled - ? "-debug" - : "", - this.privilegedAboutProcessEnabled ? "-noscripts" : "", - ".html", - ].join(""); + return "about:tor"; }
get welcomeURL() { diff --git a/browser/components/preferences/home.inc.xhtml b/browser/components/preferences/home.inc.xhtml index 5bb936782ed96..e812d969837e5 100644 --- a/browser/components/preferences/home.inc.xhtml +++ b/browser/components/preferences/home.inc.xhtml @@ -33,7 +33,7 @@ class="check-home-page-controlled" data-preference-related="browser.startup.homepage"> <menupopup> - <menuitem value="0" data-l10n-id="home-mode-choice-default" /> + <menuitem value="0" label="&aboutTor.title;" /> <menuitem value="2" data-l10n-id="home-mode-choice-custom" /> <menuitem value="1" data-l10n-id="home-mode-choice-blank" /> </menupopup> @@ -84,7 +84,7 @@ Preferences so we need to handle setting the pref manually.--> <menulist id="newTabMode" flex="1" data-preference-related="browser.newtabpage.enabled"> <menupopup> - <menuitem value="0" data-l10n-id="home-mode-choice-default" /> + <menuitem value="0" label="&aboutTor.title;" /> <menuitem value="1" data-l10n-id="home-mode-choice-blank" /> </menupopup> </menulist> diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index aab4a9e558bc2..32184867ac179 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -13,7 +13,10 @@ <?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
-<!DOCTYPE html> +<!DOCTYPE html [ +<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd"> + %aboutTorDTD; +]>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 8f52da54f7b96..d4068cabb4ae0 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -239,6 +239,8 @@ @RESPATH@/browser/chrome/browser.manifest @RESPATH@/chrome/pdfjs.manifest @RESPATH@/chrome/pdfjs/* +@RESPATH@/chrome/torbutton.manifest +@RESPATH@/chrome/torbutton/* @RESPATH@/chrome/toolkit@JAREXT@ @RESPATH@/chrome/toolkit.manifest @RESPATH@/chrome/recording.manifest diff --git a/browser/modules/HomePage.jsm b/browser/modules/HomePage.jsm index f73b0f3e6c8c8..26618374df3a0 100644 --- a/browser/modules/HomePage.jsm +++ b/browser/modules/HomePage.jsm @@ -21,7 +21,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { });
const kPrefName = "browser.startup.homepage"; -const kDefaultHomePage = "about:home"; +const kDefaultHomePage = "about:tor"; const kExtensionControllerPref = "browser.startup.homepage_override.extensionControlled"; const kHomePageIgnoreListId = "homepage-urls"; diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp index a320b4ebd4317..6ab1a57f92cf6 100644 --- a/docshell/base/nsAboutRedirector.cpp +++ b/docshell/base/nsAboutRedirector.cpp @@ -158,7 +158,11 @@ static const RedirEntry kRedirMap[] = { {"crashcontent", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::URI_CAN_LOAD_IN_CHILD | - nsIAboutModule::URI_MUST_LOAD_IN_CHILD}}; + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"tor", "chrome://torbutton/content/aboutTor/aboutTor.xhtml", + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT}}; static const int kRedirTotal = mozilla::ArrayLength(kRedirMap);
NS_IMETHODIMP diff --git a/docshell/build/components.conf b/docshell/build/components.conf index 9987b60fa2ec7..475546757fd4e 100644 --- a/docshell/build/components.conf +++ b/docshell/build/components.conf @@ -29,6 +29,7 @@ about_pages = [ 'srcdoc', 'support', 'telemetry', + 'tor', 'url-classifier', 'webrtc', ] diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index f0664be7b6ee7..dc65078b70146 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -132,6 +132,10 @@ @BINPATH@/chrome/devtools@JAREXT@ @BINPATH@/chrome/devtools.manifest
+; Torbutton +@BINPATH@/chrome/torbutton@JAREXT@ +@BINPATH@/chrome/torbutton.manifest + ; [Default Preferences] ; All the pref files must be part of base to prevent migration bugs #ifndef MOZ_ANDROID_FAT_AAR_ARCHITECTURES diff --git a/toolkit/moz.build b/toolkit/moz.build index 14f4638b693ec..4edccfac6d628 100644 --- a/toolkit/moz.build +++ b/toolkit/moz.build @@ -22,6 +22,7 @@ DIRS += [ "mozapps/preferences", "profile", "themes", + "torproject/torbutton", ]
if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["MOZ_DEFAULT_BROWSER_AGENT"]: diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index c1cef2814b386..8e16e236b238d 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1476,6 +1476,15 @@ var XPIStates = { for (let [id, file] of loc.readAddons()) { knownIds.delete(id);
+ // Uninstall torbutton if it is installed in the user profile + if (id === "torbutton@torproject.org" && + loc.name === KEY_APP_PROFILE) { + logger.debug("Uninstalling torbutton from user profile."); + loc.installer.uninstallAddon(id); + changed = true; + continue; + } + let xpiState = loc.get(id); if (!xpiState) { // If the location is not supported for sideloading, skip new diff --git a/toolkit/torproject/torbutton b/toolkit/torproject/torbutton new file mode 160000 index 0000000000000..f1df5ea1890d3 --- /dev/null +++ b/toolkit/torproject/torbutton @@ -0,0 +1 @@ +Subproject commit f1df5ea1890d35be7d557bf227028fce0eb1a8da diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js index 76e03f2d49bbf..2ff107b553b2a 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js @@ -75,7 +75,11 @@ function getGlobalScriptIncludes(scriptPath) { "browser/components/search/content/" ) .replace("chrome://browser/content/", "browser/base/content/") - .replace("chrome://global/content/", "toolkit/content/"); + .replace("chrome://global/content/", "toolkit/content/") + .replace( + "chrome://torbutton/content/", + "toolkit/torproject/torbutton/chrome/content/" + );
for (let mapping of Object.getOwnPropertyNames(MAPPINGS)) { if (sourceFile.includes(mapping)) {
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit eb8057e5d87ad5e5b2a0eb1037a2c3ea314c47ce Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue Feb 26 10:07:17 2019 -0500
Bug 28044: Integrate Tor Launcher into tor-browser
Build and package Tor Launcher as part of the browser (similar to how pdfjs is handled).
If a Tor Launcher extension is present in the user's profile, it is removed. --- browser/extensions/moz.build | 3 +++ browser/installer/package-manifest.in | 5 +++++ mozconfig-linux-x86_64-dev | 2 +- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 10 ++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build index 339702b90a8a9..d76a9f93d9af7 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -10,3 +10,6 @@ if CONFIG["NIGHTLY_BUILD"]: DIRS += [ "translations", ] + +if not CONFIG["TOR_BROWSER_DISABLE_TOR_LAUNCHER"]: + DIRS += ["tor-launcher"] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index d4068cabb4ae0..d46707ca87204 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -239,6 +239,11 @@ @RESPATH@/browser/chrome/browser.manifest @RESPATH@/chrome/pdfjs.manifest @RESPATH@/chrome/pdfjs/* +#ifndef TOR_BROWSER_DISABLE_TOR_LAUNCHER +@RESPATH@/browser/chrome/torlauncher.manifest +@RESPATH@/browser/chrome/torlauncher/* +@RESPATH@/browser/@PREF_DIR@/torlauncher-prefs.js +#endif @RESPATH@/chrome/torbutton.manifest @RESPATH@/chrome/torbutton/* @RESPATH@/chrome/toolkit@JAREXT@ diff --git a/mozconfig-linux-x86_64-dev b/mozconfig-linux-x86_64-dev index 14488d870ddb0..ce66cd7cdc3ee 100644 --- a/mozconfig-linux-x86_64-dev +++ b/mozconfig-linux-x86_64-dev @@ -9,6 +9,6 @@ ac_add_options --enable-default-toolkit=cairo-gtk3 ac_add_options --disable-strip ac_add_options --disable-install-strip
-ac_add_options --disable-tor-launcher +ac_add_options --enable-tor-launcher ac_add_options --disable-tor-browser-update ac_add_options --with-tor-browser-version=dev-build diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 8e16e236b238d..04d57a42348e7 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1485,6 +1485,16 @@ var XPIStates = { continue; }
+ // Since it is now part of the browser, uninstall the Tor Launcher + // extension. This will remove the Tor Launcher .xpi from user + // profiles on macOS. + if (id === "tor-launcher@torproject.org") { + logger.debug("Uninstalling the Tor Launcher extension."); + loc.installer.uninstallAddon(id); + changed = true; + continue; + } + let xpiState = loc.get(id); if (!xpiState) { // If the location is not supported for sideloading, skip new
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 0df3bae087129c48ab7eec3c309c3773d1200aa5 Author: Amogh Pradeep amoghbl1@gmail.com AuthorDate: Fri Jun 12 02:07:45 2015 -0400
Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
See Bug 1357997 for partial uplift.
Also: Bug 28051 - Use our Orbot for proxying our connections
Bug 31144 - ESR68 Network Code Review --- .../java/org/mozilla/gecko/util/ProxySelector.java | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java index dbd07a069de1f..800c7cf96de8b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java @@ -29,6 +29,10 @@ import java.net.URLConnection; import java.util.List;
public class ProxySelector { + private static final String TOR_PROXY_ADDRESS = "127.0.0.1"; + private static final int TOR_SOCKS_PROXY_PORT = 9150; + private static final int TOR_HTTP_PROXY_PORT = 8218; + public static URLConnection openConnectionWithProxy(final URI uri) throws IOException { final java.net.ProxySelector ps = java.net.ProxySelector.getDefault(); Proxy proxy = Proxy.NO_PROXY; @@ -39,7 +43,26 @@ public class ProxySelector { } }
- return uri.toURL().openConnection(proxy); + /* Ignore the proxy we found from the VM, only use Tor. We can probably + * safely use the logic in this class in the future. */ + return uri.toURL().openConnection(getProxy()); + } + + public static Proxy getProxy() { + // TODO make configurable + return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TOR_PROXY_ADDRESS, TOR_SOCKS_PROXY_PORT)); + } + + public static String getProxyHostAddress() { + return TOR_PROXY_ADDRESS; + } + + public static int getSocksProxyPort() { + return TOR_SOCKS_PROXY_PORT; + } + + public static int getHttpProxyPort() { + return TOR_HTTP_PROXY_PORT; }
public ProxySelector() {
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit d87f1aeae255da5884ca56dcc95c716bc1b35779 Author: Alex Catarineu acat@torproject.org AuthorDate: Fri Jul 24 21:15:20 2020 +0200
Add TorStrings module for localization --- browser/modules/TorStrings.jsm | 762 +++++++++++++++++++++++++++++++++++++++++ browser/modules/moz.build | 1 + 2 files changed, 763 insertions(+)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm new file mode 100644 index 0000000000000..e4c9515daade1 --- /dev/null +++ b/browser/modules/TorStrings.jsm @@ -0,0 +1,762 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["TorStrings"]; + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" +); +const { getLocale } = ChromeUtils.import( + "resource://torbutton/modules/utils.js" +); + +XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser"]); +XPCOMUtils.defineLazyGetter(this, "domParser", () => { + const parser = new DOMParser(); + parser.forceEnableDTD(); + return parser; +}); + +/* + Tor DTD String Bundle + + DTD strings loaded from torbutton/tor-launcher, but provide a fallback in case they aren't available +*/ +class TorDTDStringBundle { + constructor(aBundleURLs, aPrefix) { + let locations = []; + for (const [index, url] of aBundleURLs.entries()) { + locations.push(`<!ENTITY % dtd_${index} SYSTEM "${url}">%dtd_${index};`); + } + this._locations = locations; + this._prefix = aPrefix; + } + + // copied from testing/marionette/l10n.js + localizeEntity(urls, id) { + // Use the DOM parser to resolve the entity and extract its real value + let header = `<?xml version="1.0"?><!DOCTYPE elem [${this._locations.join( + "" + )}]>`; + let elem = `<elem id="elementID">&${id};</elem>`; + let doc = domParser.parseFromString(header + elem, "text/xml"); + let element = doc.querySelector("elem[id='elementID']"); + + if (element === null) { + throw new Error(`Entity with id='${id}' hasn't been found`); + } + + return element.textContent; + } + + getString(key, fallback) { + if (key) { + try { + return this.localizeEntity(this._bundleURLs, `${this._prefix}${key}`); + } catch (e) {} + } + + // on failure, assign the fallback if it exists + if (fallback) { + return fallback; + } + // otherwise return string key + return `$(${key})`; + } +} + +/* + Tor Property String Bundle + + Property strings loaded from torbutton/tor-launcher, but provide a fallback in case they aren't available +*/ +class TorPropertyStringBundle { + constructor(aBundleURL, aPrefix) { + try { + this._bundle = Services.strings.createBundle(aBundleURL); + } catch (e) {} + + this._prefix = aPrefix; + } + + getString(key, fallback) { + if (key) { + try { + return this._bundle.GetStringFromName(`${this._prefix}${key}`); + } catch (e) {} + } + + // on failure, assign the fallback if it exists + if (fallback) { + return fallback; + } + // otherwise return string key + return `$(${key})`; + } +} + +/* + Security Level Strings +*/ +var TorStrings = { + /* + Tor Browser Security Level Strings + */ + securityLevel: (function() { + let tsb = new TorDTDStringBundle( + ["chrome://torbutton/locale/torbutton.dtd"], + "torbutton.prefs.sec_" + ); + let getString = function(key, fallback) { + return tsb.getString(key, fallback); + }; + + // read localized strings from torbutton; but use hard-coded en-US strings as fallbacks in case of error + let retval = { + securityLevel: getString("caption", "Security Level"), + customWarning: getString("custom_warning", "Custom"), + overview: getString( + "overview", + "Disable certain web features that can be used to attack your security and anonymity." + ), + standard: { + level: getString("standard_label", "Standard"), + tooltip: getString("standard_tooltip", "Security Level : Standard"), + summary: getString( + "standard_description", + "All Tor Browser and website features are enabled." + ), + }, + safer: { + level: getString("safer_label", "Safer"), + tooltip: getString("safer_tooltip", "Security Level : Safer"), + summary: getString( + "safer_description", + "Disables website features that are often dangerous, causing some sites to lose functionality." + ), + description1: getString( + "js_on_https_sites_only", + "JavaScript is disabled on non-HTTPS sites." + ), + description2: getString( + "limit_typography", + "Some fonts and math symbols are disabled." + ), + description3: getString( + "click_to_play_media", + "Audio and video (HTML5 media), and WebGL are click-to-play." + ), + }, + safest: { + level: getString("safest_label", "Safest"), + tooltip: getString("safest_tooltip", "Security Level : Safest"), + summary: getString( + "safest_description", + "Only allows website features required for static sites and basic services. These changes affect images, media, and scripts." + ), + description1: getString( + "js_disabled", + "JavaScript is disabled by default on all sites." + ), + description2: getString( + "limit_graphics_and_typography", + "Some fonts, icons, math symbols, and images are disabled." + ), + description3: getString( + "click_to_play_media", + "Audio and video (HTML5 media), and WebGL are click-to-play." + ), + }, + custom: { + summary: getString( + "custom_summary", + "Your custom browser preferences have resulted in unusual security settings. For security and privacy reasons, we recommend you choose one of the default security levels." + ), + }, + learnMore: getString("learn_more_label", "Learn more"), + learnMoreURL: `https://tb-manual.torproject.org/$%7BgetLocale()%7D/security-settings/%60, + restoreDefaults: getString("restore_defaults", "Restore Defaults"), + advancedSecuritySettings: getString( + "advanced_security_settings", + "Advanced Security Settings\u2026" + ), + }; + return retval; + })() /* Security Level Strings */, + + /* + Tor about:preferences#connection Strings + */ + settings: (function() { + let tsb = new TorDTDStringBundle( + ["chrome://torlauncher/locale/network-settings.dtd"], + "" + ); + let getString = function(key, fallback) { + return tsb.getString(key, fallback); + }; + + let retval = { + categoryTitle: getString("torPreferences.categoryTitle", "Connection"), + // Message box + torPreferencesDescription: getString( + "torPreferences.torSettingsDescription", + "Tor Browser routes your traffic over the Tor Network, run by thousands of volunteers around the world." + ), + // Status + statusInternetLabel: getString("torPreferences.statusInternetLabel", "Internet:"), + statusInternetTest: getString("torPreferences.statusInternetTest", "Test"), + statusInternetOnline: getString("torPreferences.statusInternetOnline", "Online"), + statusInternetOffline: getString("torPreferences.statusInternetOffline", "Offline"), + statusTorLabel: getString("torPreferences.statusTorLabel", "Tor Network:"), + statusTorConnected: getString("torPreferences.statusTorConnected", "Connected"), + statusTorNotConnected: getString("torPreferences.statusTorNotConnected", "Not Connected"), + statusTorBlocked: getString("torPreferences.statusTorBlocked", "Potentially Blocked"), + learnMore: getString("torPreferences.learnMore", "Learn more"), + // Quickstart + quickstartHeading: getString("torPreferences.quickstart", "Quickstart"), + quickstartDescription: getString( + "torPreferences.quickstartDescriptionLong", + "Quickstart connects Tor Browser to the Tor Network automatically when launched, based on your last used connection settings." + ), + quickstartCheckbox : getString("torPreferences.quickstartCheckbox", "Always connect automatically"), + // Bridge settings + bridgesHeading: getString("torPreferences.bridges", "Bridges"), + bridgesDescription: getString( + "torPreferences.bridgesDescription", + "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another." + ), + bridgeLocation: getString("torPreferences.bridgeLocation", "Your location"), + bridgeLocationAutomatic: getString("torPreferences.bridgeLocationAutomatic", "Automatic"), + bridgeChooseForMe: getString("torPreferences.bridgeChooseForMe", "Choose a Bridge For Me\u2026"), + bridgeCurrent: getString("torPreferences.bridgeBadgeCurrent", "Your Current Bridges"), + bridgeId: getString("torPreferences.bridgeId", "#1 bridge: #2"), + remove: getString("torPreferences.remove", "Remove"), + bridgeDisableBuiltIn: getString("torPreferences.bridgeDisableBuiltIn", "Disable built-in bridges"), + bridgeShare: getString( + "torPreferences.bridgeShare", + "Share your bridge by presenting the QR code or copying its bridge line." + ), + bridgeCopy: getString("torPreferences.bridgeCopy", "Copy Bridge Address"), + copied: getString("torPreferences.copied", "Copied!"), + bridgeShowAll: getString("torPreferences.bridgeShowAll", "Show all bridges"), + bridgeRemoveAll: getString("torPreferences.bridgeRemoveAll", "Remove all bridges"), + bridgeAdd: getString("torPreferences.bridgeAdd", "Add a New Bridge"), + bridgeSelectBrowserBuiltin: getString( + "torPreferences.bridgeSelectBrowserBuiltin", + "Choose from one of Tor Browser’s built-in bridges" + ), + bridgeSelectBuiltin: getString( + "torPreferences.bridgeSelectBuiltin", + "Select a Built-In Bridge\u2026" + ), + bridgeRequestFromTorProject: getString( + "torsettings.useBridges.bridgeDB", + "Request a bridge from torproject.org" + ), + bridgeRequest: getString( + "torPreferences.bridgeRequest", + "Request a Bridge\u2026" + ), + bridgeEnterKnown: getString( + "torPreferences.bridgeEnterKnown", + "Enter a bridge address you already know" + ), + bridgeAddManually: getString( + "torPreferences.bridgeAddManually", + "Add a Bridge Manually\u2026" + ), + // Advanced settings + advancedHeading: getString("torPreferences.advanced", "Advanced"), + advancedLabel: getString( + "torPreferences.advancedDescription", + "Configure how Tor Browser connects to the internet" + ), + advancedButton: getString("torPreferences.advancedButton", "Settings\u2026"), + showTorDaemonLogs: getString( + "torPreferences.viewTorLogs", + "View the Tor logs" + ), + showLogs: getString("torPreferences.viewLogs", "View Logs\u2026"), + // Scan bridge QR dialog + scanQrTitle: getString("torPreferences.scanQrTitle", "Scan the QR code"), + // Builtin bridges dialog + builtinBridgeTitle: getString( + "torPreferences.builtinBridgeTitle", + "Built-In Bridges" + ), + builtinBridgeHeader: getString( + "torPreferences.builtinBridgeHeader", + "Select a Built-In Bridge" + ), + builtinBridgeDescription: getString( + "torPreferences.builtinBridgeDescription", + "Tor Browser includes some specific types of bridges known as “pluggable transports”." + ), + builtinBridgeObfs4: getString( + "torPreferences.builtinBridgeObfs4", + "obfs4" + ), + builtinBridgeObfs4Description: getString( + "torPreferences.builtinBridgeObfs4Description", + "obfs4 is a type of built-in bridge that makes your Tor traffic look random. They are also less likely to be blocked than their predecessors, obfs3 bridges." + ), + builtinBridgeSnowflake: getString( + "torPreferences.builtinBridgeSnowflake", + "Snowflake" + ), + builtinBridgeSnowflakeDescription: getString( + "torPreferences.builtinBridgeSnowflakeDescription", + "Snowflake is a built-in bridge that defeats censorship by routing your connection through Snowflake proxies, ran by volunteers." + ), + builtinBridgeMeekAzure: getString( + "torPreferences.builtinBridgeMeekAzure", + "meek-azure" + ), + builtinBridgeMeekAzureDescription: getString( + "torPreferences.builtinBridgeMeekAzureDescription", + "meek-azure is a built-in bridge that makes it look like you are using a Microsoft web site instead of using Tor." + ), + // Request bridges dialog + requestBridgeDialogTitle: getString( + "torPreferences.requestBridgeDialogTitle", + "Request Bridge" + ), + submitCaptcha: getString( + "torsettings.useBridges.captchaSubmit", + "Submit" + ), + contactingBridgeDB: getString( + "torPreferences.requestBridgeDialogWaitPrompt", + "Contacting BridgeDB. Please Wait." + ), + solveTheCaptcha: getString( + "torPreferences.requestBridgeDialogSolvePrompt", + "Solve the CAPTCHA to request a bridge." + ), + captchaTextboxPlaceholder: getString( + "torsettings.useBridges.captchaSolution.placeholder", + "Enter the characters from the image" + ), + incorrectCaptcha: getString( + "torPreferences.requestBridgeErrorBadSolution", + "The solution is not correct. Please try again." + ), + // Provide bridge dialog + provideBridgeTitle: getString( + "torPreferences.provideBridgeTitle", + "Provide Bridge" + ), + provideBridgeHeader: getString( + "torPreferences.provideBridgeHeader", + "Enter bridge information from a trusted source" + ), + provideBridgePlaceholder: getString( + "torsettings.bridgePlaceholder", + "type address:port (one per line)" + ), + // Connection settings dialog + connectionSettingsDialogTitle: getString( + "torPreferences.connectionSettingsDialogTitle", + "Connection Settings" + ), + connectionSettingsDialogHeader: getString( + "torPreferences.connectionSettingsDialogHeader", + "Configure how Tor Browser connects to the Internet" + ), + useLocalProxy: getString("torsettings.useProxy.checkbox", "I use a proxy to connect to the Internet"), + proxyType: getString("torsettings.useProxy.type", "Proxy Type"), + proxyTypeSOCKS4: getString("torsettings.useProxy.type.socks4", "SOCKS4"), + proxyTypeSOCKS5: getString("torsettings.useProxy.type.socks5", "SOCKS5"), + proxyTypeHTTP: getString("torsettings.useProxy.type.http", "HTTP/HTTPS"), + proxyAddress: getString("torsettings.useProxy.address", "Address"), + proxyAddressPlaceholder: getString( + "torsettings.useProxy.address.placeholder", + "IP address or hostname" + ), + proxyPort: getString("torsettings.useProxy.port", "Port"), + proxyUsername: getString("torsettings.useProxy.username", "Username"), + proxyPassword: getString("torsettings.useProxy.password", "Password"), + proxyUsernamePasswordPlaceholder: getString( + "torsettings.optional", + "Optional" + ), + useFirewall: getString( + "torsettings.firewall.checkbox", + "This computer goes through a firewall that only allows connections to certain ports" + ), + allowedPorts: getString( + "torsettings.firewall.allowedPorts", + "Allowed Ports" + ), + allowedPortsPlaceholder: getString( + "torPreferences.firewallPortsPlaceholder", + "Comma-seperated values" + ), + // Log dialog + torLogDialogTitle: getString( + "torPreferences.torLogsDialogTitle", + "Tor Logs" + ), + copyLog: getString("torsettings.copyLog", "Copy Tor Log to Clipboard"), + + learnMoreTorBrowserURL: `https://tb-manual.torproject.org/$%7BgetLocale()%7D/about/%60, + learnMoreBridgesURL: `https://tb-manual.torproject.org/$%7BgetLocale()%7D/bridges/%60, + }; + + return retval; + })() /* Tor Network Settings Strings */, + + torConnect: (() => { + const tsbNetwork = new TorDTDStringBundle( + ["chrome://torlauncher/locale/network-settings.dtd"], + "" + ); + const tsbLauncher = new TorPropertyStringBundle( + "chrome://torlauncher/locale/torlauncher.properties", + "torlauncher." + ); + const tsbCommon = new TorPropertyStringBundle( + "chrome://global/locale/commonDialogs.properties", + "" + ); + + const getStringNet = tsbNetwork.getString.bind(tsbNetwork); + const getStringLauncher = tsbLauncher.getString.bind(tsbLauncher); + const getStringCommon = tsbCommon.getString.bind(tsbCommon); + + return { + torConnect: getStringNet( + "torsettings.wizard.title.default", + "Connect to Tor" + ), + + torConnecting: getStringNet( + "torsettings.wizard.title.connecting", + "Establishing a Connection" + ), + + torNotConnectedConcise: getStringNet( + "torConnect.notConnectedConcise", + "Not Connected" + ), + + torConnectingConcise: getStringNet( + "torConnect.connectingConcise", + "Connecting…" + ), + + torBootstrapFailed: getStringLauncher( + "tor_bootstrap_failed", + "Tor failed to establish a Tor network connection." + ), + + couldNotConnect: getStringNet( + "torConnect.couldNotConnect", + "Tor Browser could not connect to Tor" + ), + + configureConnection: getStringNet( + "torConnect.assistDescriptionConfigure", + "configure your connection" + ), + + assistDescription: getStringNet( + "torConnect.assistDescription", + "If Tor is blocked in your location, trying a bridge may help. Connection assist can choose one for you using your location, or you can #1 manually instead." + ), + + tryingBridge: getStringNet( + "torConnect.tryingBridge", + "Trying a bridge…" + ), + + tryingBridgeAgain: getStringNet( + "torConnect.tryingBridge", + "Trying one more time…" + ), + + addLocation: getStringNet( + "torConnect.addLocation", + "Add your location settings" + ), + + addLocationDescription: getStringNet( + "torConnect.addLocationDescription", + "Tor Browser needs to know your location in order to choose the right bridge for you. If you’d rather not share your location, #1 manually instead." + ), + + errorLocation: getStringNet( + "torConnect.errorLocation", + "Tor Browser couldn’t locate you" + ), + + errorLocationDescription: getStringNet( + "torConnect.errorLocationDescription", + "Tor Browser still couldn’t connect to Tor. Please check your location settings are correct and try again." + ), + + finalError: getStringNet( + "torConnect.finalError", + "Tor Browser still cannot connect", + ), + + finalErrorDescription: getStringNet( + "torConnect.finalErrorDescription", + "Despite its best efforts, connection assist was not able to connect to Tor. Try troubleshooting your connection and adding a bridge manually instead.", + ), + + breadcrumbAssist: getStringNet( + "torConnect.breadcrumbAssist", + "Connection assist" + ), + + breadcrumbLocation: getStringNet( + "torConnect.breadcrumbLocation", + "Location settings" + ), + + breadcrumbTryBridge: getStringNet( + "torConnect.breadcrumbTryBridge", + "Try a bridge" + ), + + restartTorBrowser: getStringNet( + "torConnect.restartTorBrowser", + "Restart Tor Browser" + ), + + torConfigure: getStringNet( + "torConnect.configureConnection", + "Configure Connection…" + ), + + viewLog: getStringNet( + "torConnect.viewLog", + "View logs…" + ), + + torConnectButton: getStringNet("torSettings.connect", "Connect"), + + cancel: getStringCommon("Cancel", "Cancel"), + + torConnected: getStringLauncher( + "torlauncher.bootstrapStatus.done", + "Connected to the Tor network" + ), + + torConnectedConcise: getStringLauncher( + "torConnect.connectedConcise", + "Connected" + ), + + tryAgain: getStringNet("torConnect.tryAgain", "Try connecting again"), + offline: getStringNet("torConnect.offline", "Offline"), + + // tor connect strings for message box in about:preferences#connection + connectMessage: getStringNet("torConnect.connectMessage", "Changes to Tor Settings will not take effect until you connect"), + tryAgainMessage: getStringNet("torConnect.tryAgainMessage", "Tor Browser has failed to establish a connection to the Tor Network"), + + yourLocation: getStringNet("torConnect.yourLocation", "Your Location"), + + tryBridge: getStringNet("torConnect.tryBridge", "Try a Bridge"), + + selectCountryRegion: getStringNet( + "torConnect.selectCountryRegion", + "Select Country or Region", + ), + + // TorConnect.jsm error messages + autoBootstrappingFailed: getStringNet( + "torConnect.autoBootstrappingFailed", + "Automatic configuration failed" + ), + autoBootstrappingAllFailed: getStringNet( + "torConnect.autoBootstrappingFailed", + "None of the configurations we tried worked" + ), + cannotDetermineCountry: getStringNet( + "torConnect.cannotDetermineCountry", + "Unable to determine user country" + ), + noSettingsForCountry: getStringNet( + "torConnect.noSettingsForCountry", + "No settings available for your location" + ), + }; + })(), + + /* + Tor Onion Services Strings, e.g., for the authentication prompt. + */ + onionServices: (function() { + let tsb = new TorPropertyStringBundle( + "chrome://torbutton/locale/torbutton.properties", + "onionServices." + ); + let getString = function(key, fallback) { + return tsb.getString(key, fallback); + }; + + const kProblemLoadingSiteFallback = "Problem Loading Onionsite"; + const kLongDescFallback = "Details: %S"; + + let retval = { + learnMore: getString("learnMore", "Learn more"), + learnMoreURL: `https://support.torproject.org/$%7BgetLocale()%7D/onionservices/client-auth/..., + errorPage: { + browser: getString("errorPage.browser", "Browser"), + network: getString("errorPage.network", "Network"), + onionSite: getString("errorPage.onionSite", "Onionsite"), + }, + descNotFound: { // Tor SOCKS error 0xF0 + pageTitle: getString("descNotFound.pageTitle", kProblemLoadingSiteFallback), + header: getString("descNotFound.header", "Onionsite Not Found"), + longDescription: getString("descNotFound.longDescription", kLongDescFallback), + }, + descInvalid: { // Tor SOCKS error 0xF1 + pageTitle: getString("descInvalid.pageTitle", kProblemLoadingSiteFallback), + header: getString("descInvalid.header", "Onionsite Cannot Be Reached"), + longDescription: getString("descInvalid.longDescription", kLongDescFallback), + }, + introFailed: { // Tor SOCKS error 0xF2 + pageTitle: getString("introFailed.pageTitle", kProblemLoadingSiteFallback), + header: getString("introFailed.header", "Onionsite Has Disconnected"), + longDescription: getString("introFailed.longDescription", kLongDescFallback), + }, + rendezvousFailed: { // Tor SOCKS error 0xF3 + pageTitle: getString("rendezvousFailed.pageTitle", kProblemLoadingSiteFallback), + header: getString("rendezvousFailed.header", "Unable to Connect to Onionsite"), + longDescription: getString("rendezvousFailed.longDescription", kLongDescFallback), + }, + clientAuthMissing: { // Tor SOCKS error 0xF4 + pageTitle: getString("clientAuthMissing.pageTitle", "Authorization Required"), + header: getString("clientAuthMissing.header", "Onionsite Requires Authentication"), + longDescription: getString("clientAuthMissing.longDescription", kLongDescFallback), + }, + clientAuthIncorrect: { // Tor SOCKS error 0xF5 + pageTitle: getString("clientAuthIncorrect.pageTitle", "Authorization Failed"), + header: getString("clientAuthIncorrect.header", "Onionsite Authentication Failed"), + longDescription: getString("clientAuthIncorrect.longDescription", kLongDescFallback), + }, + badAddress: { // Tor SOCKS error 0xF6 + pageTitle: getString("badAddress.pageTitle", kProblemLoadingSiteFallback), + header: getString("badAddress.header", "Invalid Onionsite Address"), + longDescription: getString("badAddress.longDescription", kLongDescFallback), + }, + introTimedOut: { // Tor SOCKS error 0xF7 + pageTitle: getString("introTimedOut.pageTitle", kProblemLoadingSiteFallback), + header: getString("introTimedOut.header", "Onionsite Circuit Creation Timed Out"), + longDescription: getString("introTimedOut.longDescription", kLongDescFallback), + }, + authPrompt: { + description: + getString("authPrompt.description2", "%S is requesting that you authenticate."), + keyPlaceholder: getString("authPrompt.keyPlaceholder", "Enter your key"), + done: getString("authPrompt.done", "Done"), + doneAccessKey: getString("authPrompt.doneAccessKey", "d"), + invalidKey: getString("authPrompt.invalidKey", "Invalid key"), + failedToSetKey: + getString("authPrompt.failedToSetKey", "Failed to set key"), + }, + authPreferences: { + header: getString("authPreferences.header", "Onion Services Authentication"), + overview: getString("authPreferences.overview", "Some onion services require that you identify yourself with a key"), + savedKeys: getString("authPreferences.savedKeys", "Saved Keys"), + dialogTitle: getString("authPreferences.dialogTitle", "Onion Services Keys"), + dialogIntro: getString("authPreferences.dialogIntro", "Keys for the following onionsites are stored on your computer"), + onionSite: getString("authPreferences.onionSite", "Onionsite"), + onionKey: getString("authPreferences.onionKey", "Key"), + remove: getString("authPreferences.remove", "Remove"), + removeAll: getString("authPreferences.removeAll", "Remove All"), + failedToGetKeys: getString("authPreferences.failedToGetKeys", "Failed to get keys"), + failedToRemoveKey: getString("authPreferences.failedToRemoveKey", "Failed to remove key"), + }, + }; + + return retval; + })() /* Tor Onion Services Strings */, + + /* + OnionLocation + */ + onionLocation: (function() { + const tsb = new TorPropertyStringBundle( + ["chrome://torbutton/locale/torbutton.properties"], + "onionLocation." + ); + const getString = function(key, fallback) { + return tsb.getString(key, fallback); + }; + + const retval = { + alwaysPrioritize: getString( + "alwaysPrioritize", + "Always Prioritize Onionsites" + ), + alwaysPrioritizeAccessKey: getString("alwaysPrioritizeAccessKey", "a"), + notNow: getString("notNow", "Not Now"), + notNowAccessKey: getString("notNowAccessKey", "n"), + description: getString( + "description", + "Website publishers can protect users by adding a security layer. This prevents eavesdroppers from knowing that you are the one visiting that website." + ), + tryThis: getString("tryThis", "Try this: Onionsite"), + onionAvailable: getString("onionAvailable", "Onionsite available"), + learnMore: getString("learnMore", "Learn more"), + learnMoreURL: `https://tb-manual.torproject.org/$%7BgetLocale()%7D/onion-services/%60, + always: getString("always", "Always"), + askEverytime: getString("askEverytime", "Ask you every time"), + prioritizeOnionsDescription: getString( + "prioritizeOnionsDescription", + "Prioritize onionsites when they are available." + ), + onionServicesTitle: getString("onionServicesTitle", "Onion Services"), + }; + + return retval; + })() /* OnionLocation */, + + /* + Tor Deamon Configuration Key Strings + */ + + // TODO: proper camel case + configKeys: { + /* Bridge Conf Settings */ + useBridges: "UseBridges", + bridgeList: "Bridge", + /* Proxy Conf Strings */ + socks4Proxy: "Socks4Proxy", + socks5Proxy: "Socks5Proxy", + socks5ProxyUsername: "Socks5ProxyUsername", + socks5ProxyPassword: "Socks5ProxyPassword", + httpsProxy: "HTTPSProxy", + httpsProxyAuthenticator: "HTTPSProxyAuthenticator", + /* Firewall Conf Strings */ + reachableAddresses: "ReachableAddresses", + + /* BridgeDB Strings */ + clientTransportPlugin: "ClientTransportPlugin", + }, + + /* + about:config preference keys + */ + + preferenceKeys: { + defaultBridgeType: "extensions.torlauncher.default_bridge_type", + recommendedBridgeType: + "extensions.torlauncher.default_bridge_recommended_type", + }, + + /* + about:config preference branches + */ + preferenceBranches: { + defaultBridge: "extensions.torlauncher.default_bridge.", + bridgeDBBridges: "extensions.torlauncher.bridgedb_bridge.", + }, +}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index 646784690c9a6..bc543283d887b 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -156,6 +156,7 @@ EXTRA_JS_MODULES += [ "ThemeVariableMap.jsm", "TorProtocolService.jsm", "TorSettings.jsm", + "TorStrings.jsm", "TransientPrefs.jsm", "webrtcUI.jsm", "ZoomUI.jsm",
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit b87b6a504ab80ed200d8d3987e5adce13fcdafe9 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Apr 7 15:44:16 2022 +0200
fixup! Add TorStrings module for localization
Changes introduced by !275 --- browser/modules/TorStrings.jsm | 64 ++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 18 deletions(-)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm index e4c9515daade1..a20d1a404f8ec 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -230,19 +230,22 @@ var TorStrings = { ), bridgeLocation: getString("torPreferences.bridgeLocation", "Your location"), bridgeLocationAutomatic: getString("torPreferences.bridgeLocationAutomatic", "Automatic"), + bridgeLocationFrequent: getString("torPreferences.bridgeLocationFrequent", "Frequently selected locations"), + bridgeLocationOther: getString("torPreferences.bridgeLocationOther", "Other locations"), bridgeChooseForMe: getString("torPreferences.bridgeChooseForMe", "Choose a Bridge For Me\u2026"), bridgeCurrent: getString("torPreferences.bridgeBadgeCurrent", "Your Current Bridges"), + bridgeCurrentDescription: getString("torPreferences.bridgeBadgeCurrentDescription", "You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed."), bridgeId: getString("torPreferences.bridgeId", "#1 bridge: #2"), remove: getString("torPreferences.remove", "Remove"), bridgeDisableBuiltIn: getString("torPreferences.bridgeDisableBuiltIn", "Disable built-in bridges"), bridgeShare: getString( "torPreferences.bridgeShare", - "Share your bridge by presenting the QR code or copying its bridge line." + "Share this bridge using the QR code or by copying its address:" ), bridgeCopy: getString("torPreferences.bridgeCopy", "Copy Bridge Address"), copied: getString("torPreferences.copied", "Copied!"), - bridgeShowAll: getString("torPreferences.bridgeShowAll", "Show all bridges"), - bridgeRemoveAll: getString("torPreferences.bridgeRemoveAll", "Remove all bridges"), + bridgeShowAll: getString("torPreferences.bridgeShowAll", "Show All Bridges"), + bridgeRemoveAll: getString("torPreferences.bridgeRemoveAll", "Remove All Bridges"), bridgeAdd: getString("torPreferences.bridgeAdd", "Add a New Bridge"), bridgeSelectBrowserBuiltin: getString( "torPreferences.bridgeSelectBrowserBuiltin", @@ -280,6 +283,10 @@ var TorStrings = { "View the Tor logs" ), showLogs: getString("torPreferences.viewLogs", "View Logs\u2026"), + // Remove all bridges dialog + removeBridgesQuestion: getString("torPreferences.removeBridgesQuestion", "Remove all the bridges?"), + removeBridgesWarning: getString("torPreferences.removeBridgesWarning", "This action cannot be undone."), + cancel: getString("torPreferences.cancel", "Cancel"), // Scan bridge QR dialog scanQrTitle: getString("torPreferences.scanQrTitle", "Scan the QR code"), // Builtin bridges dialog @@ -448,6 +455,18 @@ var TorStrings = { "Connecting…" ),
+ tryingAgain: getStringNet("torConnect.tryingAgain", "Trying again…"), + + noInternet: getStringNet( + "torConnect.noInternet", + "Tor Browser couldn’t reach the Internet" + ), + + noInternetDescription: getStringNet( + "torConnect.noInternetDescription", + "This could be due to a connection issue rather than Tor being blocked. Check your Internet connection, proxy and firewall settings before trying again.", + ), + torBootstrapFailed: getStringLauncher( "tor_bootstrap_failed", "Tor failed to establish a Tor network connection." @@ -474,20 +493,10 @@ var TorStrings = { ),
tryingBridgeAgain: getStringNet( - "torConnect.tryingBridge", + "torConnect.tryingBridgeAgain", "Trying one more time…" ),
- addLocation: getStringNet( - "torConnect.addLocation", - "Add your location settings" - ), - - addLocationDescription: getStringNet( - "torConnect.addLocationDescription", - "Tor Browser needs to know your location in order to choose the right bridge for you. If you’d rather not share your location, #1 manually instead." - ), - errorLocation: getStringNet( "torConnect.errorLocation", "Tor Browser couldn’t locate you" @@ -495,7 +504,17 @@ var TorStrings = {
errorLocationDescription: getStringNet( "torConnect.errorLocationDescription", - "Tor Browser still couldn’t connect to Tor. Please check your location settings are correct and try again." + "Tor Browser needs to know your location in order to choose the right bridge for you. If you’d rather not share your location, #1 manually instead." + ), + + isLocationCorrect: getStringNet( + "torConnect.isLocationCorrect", + "Are these location settings correct?" + ), + + isLocationCorrectDescription: getStringNet( + "torConnect.isLocationCorrectDescription", + "Tor Browser still couldn’t connect to Tor. Please check your location settings are correct and try again, or #1 instead." ),
finalError: getStringNet( @@ -552,8 +571,7 @@ var TorStrings = { "Connected" ),
- tryAgain: getStringNet("torConnect.tryAgain", "Try connecting again"), - offline: getStringNet("torConnect.offline", "Offline"), + tryAgain: getStringNet("torConnect.tryAgain", "Try Again"),
// tor connect strings for message box in about:preferences#connection connectMessage: getStringNet("torConnect.connectMessage", "Changes to Tor Settings will not take effect until you connect"), @@ -563,12 +581,22 @@ var TorStrings = {
tryBridge: getStringNet("torConnect.tryBridge", "Try a Bridge"),
+ automatic: getStringNet("torConnect.automatic", "Automatic"), selectCountryRegion: getStringNet( "torConnect.selectCountryRegion", - "Select Country or Region", + "Select Country or Region" + ), + frequentLocations: getStringNet( + "torConnect.frequentLocations", + "Frequently selected locations" + ), + otherLocations: getStringNet( + "torConnect.otherLocations", + "Other locations" ),
// TorConnect.jsm error messages + offline: getStringNet("torConnect.offline", "Internet not reachable"), autoBootstrappingFailed: getStringNet( "torConnect.autoBootstrappingFailed", "Automatic configuration failed"
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 380e9bb0fae3dcfccb595393113bc2cebc2c842f Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon Jun 6 15:24:26 2022 +0200
fixup! Add TorStrings module for localization
Strings for about:rulesets --- browser/modules/TorStrings.jsm | 76 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm index a20d1a404f8ec..f2c4c53b3270f 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -747,6 +747,82 @@ var TorStrings = { return retval; })() /* OnionLocation */,
+ /* + Rulesets + */ + rulesets: (() => { + const tsb = new TorPropertyStringBundle( + ["chrome://torbutton/locale/torbutton.properties"], + "rulesets." + ); + const getString/*(key, fallback)*/ = tsb.getString; + + const retval = { + // Initial warning + warningTitle: getString("warningTitle", "Proceed with Caution"), + warningDescription: getString( + "warningDescription", + "Adding or modifying rulesets can cause attackers to hijack your browser. Proceed only if you know what you are doing." + ), + warningEnable: getString( + "warningEnable", + "Warn me when I attempt to access these preferences", + ), + warningButton: getString("warningButton", "Accept the Risk and Continue"), + // Ruleset list + rulesets: getString("rulesets", "Rulesets"), + noRulesets: getString("noRulesets", "No rulesets found"), + noRulesetsDescr: getString( + "noRulesetsDescr", + "When you save a ruleset in Tor Browser, it will show up here." + ), + lastUpdated: getString("lastUpdated", "Last updated %S"), + neverUpdated: getString( + "neverUpdated", + "Never updated, or last update failed" + ), + enabled: getString("enabled", "Enabled"), + disabled: getString("disabled", "Disabled"), + // Ruleset details + edit: getString("edit", "Edit"), + name: getString("name", "Name"), + jwk: getString("jwk", "JWK"), + pathPrefix: getString("pathPrefix", "Path Prefix"), + scope: getString("scope", "Scope"), + enable: getString("enable", "Enable this ruleset"), + checkUpdates: getString("checkUpdates", "Check for Updates"), + // Add ruleset + jwkPlaceholder: getString( + "jwkPlaceholder", + "The key used to sign this ruleset in the JWK (JSON Web Key) format" + ), + jwkInvalid: getString( + "jwkInvalid", + "The JWK could not be parsed, or it is not a valid key" + ), + pathPrefixPlaceholder: getString( + "pathPrefixPlaceholder", + "URL prefix that contains the files needed by the ruleset" + ), + pathPrefixInvalid: getString( + "pathPrefixInvalid", + "The path prefix is not a valid HTTP(S) URL" + ), + scopePlaceholder: getString( + "scopePlaceholder", + "Regular expression for the scope of the rules" + ), + scopeInvalid: getString( + "scopeInvalid", + "The scope could not be parsed as a regular expression" + ), + save: getString("save", "Save"), + cancel: getString("cancel", "Cancel"), + }; + + return retval; + })() /* Rulesets */, + /* Tor Deamon Configuration Key Strings */
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit ea7c8d9d6a1f38306e4532949207a3b2da280c98 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue Feb 24 13:50:23 2015 -0500
Bug 14631: Improve profile access error messages.
Instead of always reporting that the profile is locked, display specific messages for "access denied" and "read-only file system".
To allow for localization, get profile-related error strings from Torbutton. Use app display name ("Tor Browser") in profile-related error alerts. --- .../mozapps/profile/profileSelection.properties | 5 + toolkit/profile/nsToolkitProfileService.cpp | 57 +++++++- toolkit/profile/nsToolkitProfileService.h | 13 +- toolkit/xre/nsAppRunner.cpp | 157 ++++++++++++++++++--- 4 files changed, 208 insertions(+), 24 deletions(-)
diff --git a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties index d326083202b21..aa38bda24347c 100644 --- a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties +++ b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties @@ -12,6 +12,11 @@ restartMessageUnlocker=%S is already running, but is not responding. The old %S restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time. restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.
+# LOCALIZATION NOTE (profileProblemTitle, profileReadOnly, profileReadOnlyMac, profileAccessDenied): Messages displayed when the browser profile cannot be accessed or written to. %S is the application name. +profileProblemTitle=%S Profile Problem +profileReadOnly=You cannot run %S from a read-only file system. Please copy %S to another location before trying to use it. +profileReadOnlyMac=You cannot run %S from a read-only file system. Please copy %S to your Desktop or Applications folder before trying to use it. +profileAccessDenied=%S does not have permission to access the profile. Please adjust your file system permissions and try again. # Profile manager # LOCALIZATION NOTE (profileTooltip): First %S is the profile name, second %S is the path to the profile folder. profileTooltip=Profile: ‘%S’ — Path: ‘%S’ diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp index b2920c88345df..1a5c80a86af3f 100644 --- a/toolkit/profile/nsToolkitProfileService.cpp +++ b/toolkit/profile/nsToolkitProfileService.cpp @@ -1247,9 +1247,10 @@ nsToolkitProfileService::SelectStartupProfile( }
bool wasDefault; + ProfileStatus profileStatus; nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir, - aProfile, aDidCreate, &wasDefault); + aProfile, aDidCreate, &wasDefault, profileStatus);
// Since we were called outside of the normal startup path complete any // startup tasks. @@ -1282,7 +1283,8 @@ nsToolkitProfileService::SelectStartupProfile( nsresult nsToolkitProfileService::SelectStartupProfile( int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate, - bool* aWasDefaultSelection) { + bool* aWasDefaultSelection, ProfileStatus& aProfileStatus) { + aProfileStatus = PROFILE_STATUS_OK; if (mStartupProfileSelected) { return NS_ERROR_ALREADY_INITIALIZED; } @@ -1375,6 +1377,13 @@ nsresult nsToolkitProfileService::SelectStartupProfile( rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf)); NS_ENSURE_SUCCESS(rv, rv);
+ aProfileStatus = CheckProfileWriteAccess(lf); + if (PROFILE_STATUS_OK != aProfileStatus) { + NS_ADDREF(*aRootDir = lf); + NS_ADDREF(*aLocalDir = lf); + return NS_ERROR_FAILURE; + } + // Make sure that the profile path exists and it's a directory. bool exists; rv = lf->Exists(&exists); @@ -2169,3 +2178,47 @@ nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) { # error Platform-specific logic needed here. #endif } + +// Check for write permission to the profile directory by trying to create a +// new file (after ensuring that no file with the same name exists). +ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess( + nsIFile* aProfileDir) { +#if defined(XP_UNIX) + constexpr auto writeTestFileName = u".parentwritetest"_ns; +#else + constexpr auto writeTestFileName = u"parent.writetest"_ns; +#endif + + nsCOMPtr<nsIFile> writeTestFile; + nsresult rv = aProfileDir->Clone(getter_AddRefs(writeTestFile)); + if (NS_SUCCEEDED(rv)) rv = writeTestFile->Append(writeTestFileName); + + if (NS_SUCCEEDED(rv)) { + bool doesExist = false; + rv = writeTestFile->Exists(&doesExist); + if (NS_SUCCEEDED(rv) && doesExist) rv = writeTestFile->Remove(true); + } + + if (NS_SUCCEEDED(rv)) { + rv = writeTestFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + (void)writeTestFile->Remove(true); + } + + ProfileStatus status = + NS_SUCCEEDED(rv) ? PROFILE_STATUS_OK : PROFILE_STATUS_OTHER_ERROR; + if (NS_ERROR_FILE_ACCESS_DENIED == rv) + status = PROFILE_STATUS_ACCESS_DENIED; + else if (NS_ERROR_FILE_READ_ONLY == rv) + status = PROFILE_STATUS_READ_ONLY; + + return status; +} + +ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess( + nsIToolkitProfile* aProfile) { + nsCOMPtr<nsIFile> profileDir; + nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir)); + if (NS_FAILED(rv)) return PROFILE_STATUS_OTHER_ERROR; + + return CheckProfileWriteAccess(profileDir); +} diff --git a/toolkit/profile/nsToolkitProfileService.h b/toolkit/profile/nsToolkitProfileService.h index d281d39ebe597..5c97c906df497 100644 --- a/toolkit/profile/nsToolkitProfileService.h +++ b/toolkit/profile/nsToolkitProfileService.h @@ -16,6 +16,14 @@ #include "nsProfileLock.h" #include "nsINIParser.h"
+enum ProfileStatus { + PROFILE_STATUS_OK, + PROFILE_STATUS_ACCESS_DENIED, + PROFILE_STATUS_READ_ONLY, + PROFILE_STATUS_IS_LOCKED, + PROFILE_STATUS_OTHER_ERROR +}; + class nsToolkitProfile final : public nsIToolkitProfile, public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> { @@ -80,10 +88,13 @@ class nsToolkitProfileService final : public nsIToolkitProfileService { nsresult SelectStartupProfile(int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate, - bool* aWasDefaultSelection); + bool* aWasDefaultSelection, + ProfileStatus& aProfileStatus); nsresult CreateResetProfile(nsIToolkitProfile** aNewProfile); nsresult ApplyResetProfile(nsIToolkitProfile* aOldProfile); void CompleteStartup(); + static ProfileStatus CheckProfileWriteAccess(nsIToolkitProfile* aProfile); + static ProfileStatus CheckProfileWriteAccess(nsIFile* aProfileDir);
private: friend class nsToolkitProfile; diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 90bf00811b646..1b7e7bdf1e593 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2283,6 +2283,91 @@ nsresult LaunchChild(bool aBlankCommandLine, bool aTryExec) { return NS_ERROR_LAUNCHED_CHILD_PROCESS; }
+static nsresult GetOverrideStringBundleForLocale(nsIStringBundleService* aSBS, + const char* aTorbuttonURI, + const char* aLocale, + nsIStringBundle** aResult) { + NS_ENSURE_ARG(aSBS); + NS_ENSURE_ARG(aTorbuttonURI); + NS_ENSURE_ARG(aLocale); + NS_ENSURE_ARG(aResult); + + const char* kFormatStr = + "jar:%s!/chrome/torbutton/locale/%s/torbutton.properties"; + nsPrintfCString strBundleURL(kFormatStr, aTorbuttonURI, aLocale); + nsresult rv = aSBS->CreateBundle(strBundleURL.get(), aResult); + NS_ENSURE_SUCCESS(rv, rv); + + // To ensure that we have a valid string bundle, try to retrieve a string + // that we know exists. + nsAutoString val; + rv = (*aResult)->GetStringFromName("profileProblemTitle", val); + if (!NS_SUCCEEDED(rv)) *aResult = nullptr; // No good. Discard it. + + return rv; +} + +static void GetOverrideStringBundle(nsIStringBundleService* aSBS, + nsIStringBundle** aResult) { + if (!aSBS || !aResult) return; + + *aResult = nullptr; + + // Build Torbutton file URI string by starting from GREDir. + RefPtr<nsXREDirProvider> dirProvider = nsXREDirProvider::GetSingleton(); + if (!dirProvider) return; + + nsCOMPtr<nsIFile> greDir = dirProvider->GetGREDir(); + if (!greDir) return; + + // Create file URI, extract as string, and append omni.ja relative path. + nsCOMPtr<nsIURI> uri; + nsAutoCString uriString; + if (NS_FAILED(NS_NewFileURI(getter_AddRefs(uri), greDir)) || + NS_FAILED(uri->GetSpec(uriString))) { + return; + } + + uriString.Append("omni.ja"); + + nsAutoCString userAgentLocale; + if (!NS_SUCCEEDED( + Preferences::GetCString("intl.locale.requested", userAgentLocale))) { + return; + } + + nsresult rv = GetOverrideStringBundleForLocale( + aSBS, uriString.get(), userAgentLocale.get(), aResult); + if (NS_FAILED(rv)) { + // Try again using base locale, e.g., "en" vs. "en-US". + int16_t offset = userAgentLocale.FindChar('-', 1); + if (offset > 0) { + nsAutoCString shortLocale(Substring(userAgentLocale, 0, offset)); + rv = GetOverrideStringBundleForLocale(aSBS, uriString.get(), + shortLocale.get(), aResult); + } + } +} + +static nsresult GetFormattedString(nsIStringBundle* aOverrideBundle, + nsIStringBundle* aMainBundle, + const char* aName, + const nsTArray<nsString>& aParams, + nsAString& aResult) { + NS_ENSURE_ARG(aName); + + nsresult rv = NS_ERROR_FAILURE; + if (aOverrideBundle) { + rv = aOverrideBundle->FormatStringFromName(aName, aParams, aResult); + } + + // If string was not found in override bundle, use main (browser) bundle. + if (NS_FAILED(rv) && aMainBundle) + rv = aMainBundle->FormatStringFromName(aName, aParams, aResult); + + return rv; +} + static const char kProfileProperties[] = "chrome://mozapps/locale/profile/profileSelection.properties";
@@ -2348,7 +2433,7 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) { sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE);
- NS_ConvertUTF8toUTF16 appName(gAppData->name); + NS_ConvertUTF8toUTF16 appName(MOZ_APP_DISPLAYNAME); AutoTArray<nsString, 2> params = {appName, appName};
// profileMissing @@ -2372,11 +2457,12 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
// If aUnlocker is NULL, it is also OK for the following arguments to be NULL: // aProfileDir, aProfileLocalDir, aResult. -static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir, - nsIFile* aProfileLocalDir, - nsIProfileUnlocker* aUnlocker, - nsINativeAppSupport* aNative, - nsIProfileLock** aResult) { +static ReturnAbortOnError ProfileErrorDialog(nsIFile* aProfileDir, + nsIFile* aProfileLocalDir, + ProfileStatus aStatus, + nsIProfileUnlocker* aUnlocker, + nsINativeAppSupport* aNative, + nsIProfileLock** aResult) { nsresult rv;
if (aProfileDir) { @@ -2406,24 +2492,39 @@ static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir, sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE);
- NS_ConvertUTF8toUTF16 appName(gAppData->name); + nsCOMPtr<nsIStringBundle> overrideSB; + GetOverrideStringBundle(sbs, getter_AddRefs(overrideSB)); + + NS_ConvertUTF8toUTF16 appName(MOZ_APP_DISPLAYNAME); AutoTArray<nsString, 3> params = {appName, appName, appName};
nsAutoString killMessage; #ifndef XP_MACOSX - rv = sb->FormatStringFromName( - aUnlocker ? "restartMessageUnlocker" : "restartMessageNoUnlocker2", - params, killMessage); + static const char kRestartUnlocker[] = "restartMessageUnlocker"; + static const char kRestartNoUnlocker[] = "restartMessageNoUnlocker2"; + static const char kReadOnly[] = "profileReadOnly"; #else - rv = sb->FormatStringFromName( - aUnlocker ? "restartMessageUnlockerMac" : "restartMessageNoUnlockerMac", - params, killMessage); -#endif + static const char kRestartUnlocker[] = "restartMessageUnlockerMac"; + static const char kRestartNoUnlocker[] = "restartMessageNoUnlockerMac"; + static const char kReadOnly[] = "profileReadOnlyMac"; +#endif + static const char kAccessDenied[] = "profileAccessDenied"; + + const char* errorKey = aUnlocker ? kRestartUnlocker : kRestartNoUnlocker; + if (PROFILE_STATUS_READ_ONLY == aStatus) + errorKey = kReadOnly; + else if (PROFILE_STATUS_ACCESS_DENIED == aStatus) + errorKey = kAccessDenied; + rv = GetFormattedString(overrideSB, sb, errorKey, params, killMessage); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ const char* titleKey = ((PROFILE_STATUS_READ_ONLY == aStatus) || + (PROFILE_STATUS_ACCESS_DENIED == aStatus)) + ? "profileProblemTitle" + : "restartTitle"; params.SetLength(1); nsAutoString killTitle; - rv = sb->FormatStringFromName("restartTitle", params, killTitle); + rv = sb->FormatStringFromName(titleKey, params, killTitle); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
#ifdef MOZ_BACKGROUNDTASKS @@ -2609,6 +2710,13 @@ static nsCOMPtr<nsIToolkitProfile> gResetOldProfile; static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir, nsIFile* aLocalDir, nsIToolkitProfile* aProfile, nsIProfileLock** aResult) { + ProfileStatus status = + (aProfile ? nsToolkitProfileService::CheckProfileWriteAccess(aProfile) + : nsToolkitProfileService::CheckProfileWriteAccess(aRootDir)); + if (PROFILE_STATUS_OK != status) + return ProfileErrorDialog(aRootDir, aLocalDir, status, nullptr, aNative, + aResult); + // If you close Firefox and very quickly reopen it, the old Firefox may // still be closing down. Rather than immediately showing the // "Firefox is running but is not responding" message, we spend a few @@ -2635,7 +2743,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir, } while (TimeStamp::Now() - start < TimeDuration::FromSeconds(kLockRetrySeconds));
- return ProfileLockedDialog(aRootDir, aLocalDir, unlocker, aNative, aResult); + return ProfileErrorDialog(aRootDir, aLocalDir, PROFILE_STATUS_IS_LOCKED, + unlocker, aNative, aResult); }
// Pick a profile. We need to end up with a profile root dir, local dir and @@ -2650,7 +2759,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir, static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc, nsINativeAppSupport* aNative, nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile, - bool* aWasDefaultSelection) { + bool* aWasDefaultSelection, + nsIProfileLock** aResult) { StartupTimeline::Record(StartupTimeline::SELECT_PROFILE);
nsresult rv; @@ -2696,9 +2806,14 @@ static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
// Ask the profile manager to select the profile directories to use. bool didCreate = false; - rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset, - aRootDir, aLocalDir, aProfile, - &didCreate, aWasDefaultSelection); + ProfileStatus profileStatus = PROFILE_STATUS_OK; + rv = aProfileSvc->SelectStartupProfile( + &gArgc, gArgv, gDoProfileReset, aRootDir, aLocalDir, aProfile, &didCreate, + aWasDefaultSelection, profileStatus); + if (PROFILE_STATUS_OK != profileStatus) { + return ProfileErrorDialog(*aRootDir, *aLocalDir, profileStatus, nullptr, + aNative, aResult); + }
if (rv == NS_ERROR_SHOW_PROFILE_MANAGER) { return ShowProfileManager(aProfileSvc, aNative); @@ -4528,7 +4643,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) { nsCOMPtr<nsIToolkitProfile> profile; rv = SelectProfile(mProfileSvc, mNativeApp, getter_AddRefs(mProfD), getter_AddRefs(mProfLD), getter_AddRefs(profile), - &wasDefaultSelection); + &wasDefaultSelection, getter_AddRefs(mProfileLock)); if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) { *aExitFlag = true; return 0;
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 0bee2cf9ef8d22208d3a7760be57fb6cff4f16f4 Author: sanketh me@snkth.com AuthorDate: Mon Feb 8 20:12:44 2021 -0500
40209: Implement Basic Crypto Safety
Adds a CryptoSafety actor which detects when you've copied a crypto address from a HTTP webpage and shows a warning.
Closes #40209.
Bug 40428: Fix string attribute names --- browser/actors/CryptoSafetyChild.jsm | 87 ++++++++++++++++ browser/actors/CryptoSafetyParent.jsm | 142 +++++++++++++++++++++++++++ browser/actors/moz.build | 2 + browser/base/content/popup-notifications.inc | 14 +++ browser/components/BrowserGlue.jsm | 18 ++++ browser/modules/TorStrings.jsm | 48 +++++++++ browser/themes/shared/browser.inc.css | 5 + toolkit/content/license.html | 32 ++++++ toolkit/modules/Bech32Decode.jsm | 103 +++++++++++++++++++ toolkit/modules/moz.build | 1 + 10 files changed, 452 insertions(+)
diff --git a/browser/actors/CryptoSafetyChild.jsm b/browser/actors/CryptoSafetyChild.jsm new file mode 100644 index 0000000000000..87ff261d49157 --- /dev/null +++ b/browser/actors/CryptoSafetyChild.jsm @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Copyright (c) 2020, The Tor Project, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["CryptoSafetyChild"]; + +const { Bech32Decode } = ChromeUtils.import( + "resource://gre/modules/Bech32Decode.jsm" +); + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +const kPrefCryptoSafety = "security.cryptoSafety"; + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "isCryptoSafetyEnabled", + kPrefCryptoSafety, + true /* defaults to true */ +); + +function looksLikeCryptoAddress(s) { + // P2PKH and P2SH addresses + // https://stackoverflow.com/a/24205650 + const bitcoinAddr = /^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$/; + if (bitcoinAddr.test(s)) { + return true; + } + + // Bech32 addresses + if (Bech32Decode(s) !== null) { + return true; + } + + // regular addresses + const etherAddr = /^0x[a-fA-F0-9]{40}$/; + if (etherAddr.test(s)) { + return true; + } + + // t-addresses + // https://www.reddit.com/r/zec/comments/8mxj6x/simple_regex_to_validate_a_zcas... + const zcashAddr = /^t1[a-zA-Z0-9]{33}$/; + if (zcashAddr.test(s)) { + return true; + } + + // Standard, Integrated, and 256-bit Integrated addresses + // https://monero.stackexchange.com/a/10627 + const moneroAddr = /^4(?:[0-9AB]|[1-9A-HJ-NP-Za-km-z]{12}(?:[1-9A-HJ-NP-Za-km-z]{30})?)[1-9A-HJ-NP-Za-km-z]{93}$/; + if (moneroAddr.test(s)) { + return true; + } + + return false; +} + +class CryptoSafetyChild extends JSWindowActorChild { + handleEvent(event) { + if (isCryptoSafetyEnabled) { + // Ignore non-HTTP addresses + if (!this.document.documentURIObject.schemeIs("http")) { + return; + } + // Ignore onion addresses + if (this.document.documentURIObject.host.endsWith(".onion")) { + return; + } + + if (event.type == "copy" || event.type == "cut") { + this.contentWindow.navigator.clipboard.readText().then(clipText => { + const selection = clipText.trim(); + if (looksLikeCryptoAddress(selection)) { + this.sendAsyncMessage("CryptoSafety:CopiedText", { + selection, + }); + } + }); + } + } + } +} diff --git a/browser/actors/CryptoSafetyParent.jsm b/browser/actors/CryptoSafetyParent.jsm new file mode 100644 index 0000000000000..bac151df5511c --- /dev/null +++ b/browser/actors/CryptoSafetyParent.jsm @@ -0,0 +1,142 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Copyright (c) 2020, The Tor Project, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["CryptoSafetyParent"]; + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + TorStrings: "resource:///modules/TorStrings.jsm", +}); + +const kPrefCryptoSafety = "security.cryptoSafety"; + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "isCryptoSafetyEnabled", + kPrefCryptoSafety, + true /* defaults to true */ +); + +class CryptoSafetyParent extends JSWindowActorParent { + getBrowser() { + return this.browsingContext.top.embedderElement; + } + + receiveMessage(aMessage) { + if (isCryptoSafetyEnabled) { + if (aMessage.name == "CryptoSafety:CopiedText") { + showPopup(this.getBrowser(), aMessage.data.selection); + } + } + } +} + +function trimAddress(cryptoAddr) { + if (cryptoAddr.length <= 32) { + return cryptoAddr; + } + return cryptoAddr.substring(0, 32) + "..."; +} + +function showPopup(aBrowser, cryptoAddr) { + const chromeDoc = aBrowser.ownerDocument; + if (chromeDoc) { + const win = chromeDoc.defaultView; + const cryptoSafetyPrompt = new CryptoSafetyPrompt( + aBrowser, + win, + cryptoAddr + ); + cryptoSafetyPrompt.show(); + } +} + +class CryptoSafetyPrompt { + constructor(aBrowser, aWin, cryptoAddr) { + this._browser = aBrowser; + this._win = aWin; + this._cryptoAddr = cryptoAddr; + } + + show() { + const primaryAction = { + label: TorStrings.cryptoSafetyPrompt.primaryAction, + accessKey: TorStrings.cryptoSafetyPrompt.primaryActionAccessKey, + callback: () => { + this._win.torbutton_new_circuit(); + }, + }; + + const secondaryAction = { + label: TorStrings.cryptoSafetyPrompt.secondaryAction, + accessKey: TorStrings.cryptoSafetyPrompt.secondaryActionAccessKey, + callback: () => {}, + }; + + let _this = this; + const options = { + popupIconURL: "chrome://browser/skin/cert-error.svg", + eventCallback(aTopic) { + if (aTopic === "showing") { + _this._onPromptShowing(); + } + }, + }; + + const cryptoWarningText = TorStrings.cryptoSafetyPrompt.cryptoWarning.replace( + "%S", + trimAddress(this._cryptoAddr) + ); + + if (this._win.PopupNotifications) { + this._prompt = this._win.PopupNotifications.show( + this._browser, + "crypto-safety-warning", + cryptoWarningText, + null /* anchor ID */, + primaryAction, + [secondaryAction], + options + ); + } + } + + _onPromptShowing() { + let xulDoc = this._browser.ownerDocument; + + let whatCanHeading = xulDoc.getElementById( + "crypto-safety-warning-notification-what-can-heading" + ); + if (whatCanHeading) { + whatCanHeading.textContent = TorStrings.cryptoSafetyPrompt.whatCanHeading; + } + + let whatCanBody = xulDoc.getElementById( + "crypto-safety-warning-notification-what-can-body" + ); + if (whatCanBody) { + whatCanBody.textContent = TorStrings.cryptoSafetyPrompt.whatCanBody; + } + + let learnMoreElem = xulDoc.getElementById( + "crypto-safety-warning-notification-learnmore" + ); + if (learnMoreElem) { + learnMoreElem.setAttribute( + "value", + TorStrings.cryptoSafetyPrompt.learnMore + ); + learnMoreElem.setAttribute( + "href", + TorStrings.cryptoSafetyPrompt.learnMoreURL + ); + } + } +} diff --git a/browser/actors/moz.build b/browser/actors/moz.build index 626ee52d34f26..b329f3cfb8ff6 100644 --- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -56,6 +56,8 @@ FINAL_TARGET_FILES.actors += [ "ContentSearchParent.jsm", "ContextMenuChild.jsm", "ContextMenuParent.jsm", + "CryptoSafetyChild.jsm", + "CryptoSafetyParent.jsm", "DecoderDoctorChild.jsm", "DecoderDoctorParent.jsm", "DOMFullscreenChild.jsm", diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc index 6adfde017b9e4..8f6d28cc81b2b 100644 --- a/browser/base/content/popup-notifications.inc +++ b/browser/base/content/popup-notifications.inc @@ -162,3 +162,17 @@ </vbox> </popupnotificationfooter> </popupnotification> + + <popupnotification id="crypto-safety-warning-notification" hidden="true"> + <popupnotificationcontent orient="vertical"> + <description id="crypto-safety-warning-notification-desc"/> + <html:div id="crypto-safety-warning-notification-what-can"> + <html:strong id="crypto-safety-warning-notification-what-can-heading" /> + html:br/ + <html:span id="crypto-safety-warning-notification-what-can-body" /> + </html:div> + <label id="crypto-safety-warning-notification-learnmore" + class="popup-notification-learnmore-link" + is="text-link"/> + </popupnotificationcontent> + </popupnotification> diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 58db8ff37ce90..9dfad0358ed75 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -432,6 +432,24 @@ let JSWINDOWACTORS = { },
messageManagerGroups: ["browsers"], + + allFrames: true, + }, + + CryptoSafety: { + parent: { + moduleURI: "resource:///actors/CryptoSafetyParent.jsm", + }, + + child: { + moduleURI: "resource:///actors/CryptoSafetyChild.jsm", + group: "browsers", + events: { + copy: { mozSystemGroup: true }, + cut: { mozSystemGroup: true }, + }, + }, + allFrames: true, },
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm index f2c4c53b3270f..f4e54cfad3443 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -101,6 +101,54 @@ class TorPropertyStringBundle { Security Level Strings */ var TorStrings = { + /* + CryptoSafetyPrompt Strings + */ + cryptoSafetyPrompt: (function() { + let tsb = new TorPropertyStringBundle( + "chrome://torbutton/locale/torbutton.properties", + "cryptoSafetyPrompt." + ); + let getString = function(key, fallback) { + return tsb.getString(key, fallback); + }; + + let retval = { + cryptoWarning: getString( + "cryptoWarning", + "A cryptocurrency address (%S) has been copied from an insecure website. It could have been modified." + ), + whatCanHeading: getString( + "whatCanHeading", + "What can you do about it?" + ), + whatCanBody: getString( + "whatCanBody", + "You can try reconnecting with a new circuit to establish a secure connection, or accept the risk and dismiss this warning." + ), + learnMore: getString("learnMore", "Learn more"), + learnMoreURL: `https://support.torproject.org/$%7BgetLocale()%7D/%60, + primaryAction: getString( + "primaryAction", + "Reload Tab with a New Circuit" + ), + primaryActionAccessKey: getString( + "primaryActionAccessKey", + "R" + ), + secondaryAction: getString( + "secondaryAction", + "Dismiss Warning" + ), + secondaryActionAccessKey: getString( + "secondaryActionAccessKey", + "D" + ), + }; + + return retval; + })() /* CryptoSafetyPrompt Strings */, + /* Tor Browser Security Level Strings */ diff --git a/browser/themes/shared/browser.inc.css b/browser/themes/shared/browser.inc.css index 2eeefda472d67..e70aeab1c761a 100644 --- a/browser/themes/shared/browser.inc.css +++ b/browser/themes/shared/browser.inc.css @@ -828,3 +828,8 @@ popupnotificationcontent { #tab-notification-deck { display: block; } + +#crypto-safety-warning-notification-what-can { + display: block; + margin: 5px; +} diff --git a/toolkit/content/license.html b/toolkit/content/license.html index d26dc7118d3c9..782e874edf2aa 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -70,6 +70,7 @@ <li><a href="about:license#arm">ARM License</a></li> <li><a href="about:license#babel">Babel License</a></li> <li><a href="about:license#babylon">Babylon License</a></li> + <li><a href="about:license#bech32">Bech32 License</a></li> <li><a href="about:license#bincode">bincode License</a></li> <li><a href="about:license#bsd2clause">BSD 2-Clause License</a></li> <li><a href="about:license#bsd3clause">BSD 3-Clause License</a></li> @@ -2105,6 +2106,37 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +</pre> + + + <hr> + + <h1><a id="bech32"></a>Bech32 License</h1> + + <p>This license applies to the file + <code>toolkit/modules/Bech32Decode.jsm</code>. + </p> + +<pre> +Copyright (c) 2017 Pieter Wuille + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/toolkit/modules/Bech32Decode.jsm b/toolkit/modules/Bech32Decode.jsm new file mode 100644 index 0000000000000..3a2bc7ae0a10b --- /dev/null +++ b/toolkit/modules/Bech32Decode.jsm @@ -0,0 +1,103 @@ +// Adapted from the reference implementation of Bech32 +// https://github.com/sipa/bech32 + +// Copyright (c) 2017 Pieter Wuille +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +"use strict"; + +/** + * JS module implementation of Bech32 decoding adapted from the reference + * implementation https://github.com/sipa/bech32. + */ + +var EXPORTED_SYMBOLS = ["Bech32Decode"]; + +var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; +var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + +function polymod(values) { + var chk = 1; + for (var p = 0; p < values.length; ++p) { + var top = chk >> 25; + chk = ((chk & 0x1ffffff) << 5) ^ values[p]; + for (var i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; +} + +function hrpExpand(hrp) { + var ret = []; + var p; + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5); + } + ret.push(0); + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return ret; +} + +function verifyChecksum(hrp, data) { + return polymod(hrpExpand(hrp).concat(data)) === 1; +} + +function Bech32Decode(bechString) { + var p; + var has_lower = false; + var has_upper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + has_lower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + has_upper = true; + } + } + if (has_lower && has_upper) { + return null; + } + bechString = bechString.toLowerCase(); + var pos = bechString.lastIndexOf("1"); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + var hrp = bechString.substring(0, pos); + var data = []; + for (p = pos + 1; p < bechString.length; ++p) { + var d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + if (!verifyChecksum(hrp, data)) { + return null; + } + return { hrp: hrp, data: data.slice(0, data.length - 6) }; +} diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index c6b2c421f4473..e77c33869b621 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -152,6 +152,7 @@ EXTRA_JS_MODULES += [ "ActorManagerParent.jsm", "AppMenuNotifications.jsm", "AsyncPrefs.jsm", + "Bech32Decode.jsm", "BinarySearch.jsm", "BrowserTelemetryUtils.jsm", "BrowserUtils.jsm",
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 8c2d347aba89b5b701daa6e5f4f2d34fe25684f8 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue Jun 28 15:13:05 2016 -0400
Bug 19273: Avoid JavaScript patching of the external app helper dialog.
When handling an external URI or downloading a file, invoke Torbutton's external app blocker component (which will present a download warning dialog unless the user has checked the "Automatically download files from now on" box).
For e10s compatibility, avoid using a modal dialog and instead use a callback interface (nsIHelperAppWarningLauncher) to allow Torbutton to indicate the user's desire to cancel or continue each request.
Other bugs fixed: Bug 21766: Crash with e10s enabled while trying to download a file Bug 21886: Download is stalled in non-e10s mode Bug 22471: Downloading files via the PDF viewer download button is broken Bug 22472: Fix FTP downloads when external helper app dialog is shown Bug 22610: Avoid crashes when canceling external helper app downloads Bug 22618: Downloading pdf file via file:/// is stalling --- .../exthandler/nsExternalHelperAppService.cpp | 177 ++++++++++++++++++--- uriloader/exthandler/nsExternalHelperAppService.h | 3 + .../exthandler/nsIExternalHelperAppService.idl | 47 ++++++ 3 files changed, 209 insertions(+), 18 deletions(-)
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index 122524c8ce7a0..cb225b39b9cd0 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -133,6 +133,9 @@ static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] = static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] = "browser.helperApps.neverAsk.openFile";
+static const char WARNING_DIALOG_CONTRACT_ID[] = + "@torproject.org/torbutton-extAppBlocker;1"; + // Helper functions for Content-Disposition headers
/** @@ -405,6 +408,22 @@ static nsresult GetDownloadDirectory(nsIFile** _directory, return NS_OK; }
+static already_AddRefed<nsIInterfaceRequestor> GetDialogParentAux( + BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext) { + nsCOMPtr<nsIInterfaceRequestor> dialogParent = aWindowContext; + + if (!dialogParent && aBrowsingContext) { + dialogParent = do_QueryInterface(aBrowsingContext->GetDOMWindow()); + } + if (!dialogParent && aBrowsingContext && XRE_IsParentProcess()) { + RefPtr<Element> element = aBrowsingContext->Top()->GetEmbedderElement(); + if (element) { + dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow()); + } + } + return dialogParent.forget(); +} + /** * Structure for storing extension->type mappings. * @see defaultMimeEntries @@ -609,6 +628,96 @@ static const char* descriptionOverwriteExtensions[] = { "avif", "jxl", "pdf", "svg", "webp", "xml", };
+////////////////////////////////////////////////////////////////////////////////////////////////////// +// begin nsExternalLoadURIHandler class definition and implementation +////////////////////////////////////////////////////////////////////////////////////////////////////// +class nsExternalLoadURIHandler final : public nsIHelperAppWarningLauncher { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIHELPERAPPWARNINGLAUNCHER + + nsExternalLoadURIHandler(nsIHandlerInfo* aHandlerInfo, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + BrowsingContext* aBrowsingContext, + bool aTriggeredExternally); + + protected: + ~nsExternalLoadURIHandler(); + + nsCOMPtr<nsIHandlerInfo> mHandlerInfo; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + RefPtr<BrowsingContext> mBrowsingContext; + bool mTriggeredExternally; + nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog; +}; + +NS_IMPL_ADDREF(nsExternalLoadURIHandler) +NS_IMPL_RELEASE(nsExternalLoadURIHandler) + +NS_INTERFACE_MAP_BEGIN(nsExternalLoadURIHandler) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHelperAppWarningLauncher) + NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher) +NS_INTERFACE_MAP_END + +nsExternalLoadURIHandler::nsExternalLoadURIHandler( + nsIHandlerInfo* aHandlerInfo, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, BrowsingContext* aBrowsingContext, + bool aTriggeredExternally) + : mHandlerInfo(aHandlerInfo), + mURI(aURI), + mTriggeringPrincipal(aTriggeringPrincipal), + mBrowsingContext(aBrowsingContext), + mTriggeredExternally(aTriggeredExternally) + +{ + nsresult rv = NS_OK; + mWarningDialog = do_CreateInstance(WARNING_DIALOG_CONTRACT_ID, &rv); + if (NS_SUCCEEDED(rv) && mWarningDialog) { + // This will create a reference cycle (the dialog holds a reference to us + // as nsIHelperAppWarningLauncher), which will be broken in ContinueRequest + // or CancelRequest. + nsCOMPtr<nsIInterfaceRequestor> dialogParent = + GetDialogParentAux(aBrowsingContext, nullptr); + rv = mWarningDialog->MaybeShow(this, dialogParent); + } + + if (NS_FAILED(rv)) { + // If for some reason we could not open the download warning prompt, + // continue with the request. + ContinueRequest(); + } +} + +nsExternalLoadURIHandler::~nsExternalLoadURIHandler() {} + +NS_IMETHODIMP nsExternalLoadURIHandler::ContinueRequest() { + MOZ_ASSERT(mURI); + MOZ_ASSERT(mHandlerInfo); + + // Break our reference cycle with the download warning dialog (set up in + // LoadURI). + mWarningDialog = nullptr; + + nsresult rv = NS_OK; + nsCOMPtr<nsIContentDispatchChooser> chooser = + do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return chooser->HandleURI(mHandlerInfo, mURI, mTriggeringPrincipal, + mBrowsingContext, mTriggeredExternally); +} + +NS_IMETHODIMP nsExternalLoadURIHandler::CancelRequest(nsresult aReason) { + NS_ENSURE_ARG(NS_FAILED(aReason)); + + // Break our reference cycle with the download warning dialog (set up in + // LoadURI). + mWarningDialog = nullptr; + + return NS_OK; +} + static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;
/** @@ -635,6 +744,9 @@ nsExternalHelperAppService::GetSingleton() { return do_AddRef(sExtHelperAppSvcSingleton); }
+////////////////////////////////////////////////////////////////////////////////////////////////////// +// nsExternalHelperAppService definition and implementation +////////////////////////////////////////////////////////////////////////////////////////////////////// NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService, nsPIExternalAppLauncher, nsIExternalProtocolService, nsIMIMEService, nsIObserver, nsISupportsWeakReference) @@ -1125,14 +1237,15 @@ nsExternalHelperAppService::LoadURI(nsIURI* aURI, rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler)); NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIContentDispatchChooser> chooser = - do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - - return chooser->HandleURI( + RefPtr<nsExternalLoadURIHandler> h = new nsExternalLoadURIHandler( handler, escapedURI, aRedirectPrincipal ? aRedirectPrincipal : aTriggeringPrincipal, aBrowsingContext, aTriggeredExternally); + if (!h) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; }
////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1277,6 +1390,7 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher) + NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher) NS_INTERFACE_MAP_ENTRY(nsICancelable) NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver) NS_INTERFACE_MAP_ENTRY(nsINamed) @@ -1675,18 +1789,7 @@ void nsExternalAppHandler::MaybeApplyDecodingForExtension(
already_AddRefed<nsIInterfaceRequestor> nsExternalAppHandler::GetDialogParent() { - nsCOMPtr<nsIInterfaceRequestor> dialogParent = mWindowContext; - - if (!dialogParent && mBrowsingContext) { - dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow()); - } - if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) { - RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement(); - if (element) { - dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow()); - } - } - return dialogParent.forget(); + return GetDialogParentAux(mBrowsingContext, mWindowContext); }
NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { @@ -1814,6 +1917,34 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { loadInfo->SetForceAllowDataURI(true); }
+ mWarningDialog = do_CreateInstance(WARNING_DIALOG_CONTRACT_ID, &rv); + if (NS_SUCCEEDED(rv) && mWarningDialog) { + // This will create a reference cycle (the dialog holds a reference to us + // as nsIHelperAppWarningLauncher), which will be broken in ContinueRequest + // or CancelRequest. + nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent(); + rv = mWarningDialog->MaybeShow(this, dialogParent); + } + + if (NS_FAILED(rv)) { + // If for some reason we could not open the download warning prompt, + // continue with the request. + ContinueRequest(); + } + + return NS_OK; +} + +NS_IMETHODIMP nsExternalAppHandler::ContinueRequest() { + nsAutoCString MIMEType; + if (mMimeInfo) { + mMimeInfo->GetMIMEType(MIMEType); + } + + // Break our reference cycle with the download warning dialog (set up in + // OnStartRequest). + mWarningDialog = nullptr; + // now that the temp file is set up, find out if we need to invoke a dialog // asking the user what they want us to do with this content...
@@ -1925,6 +2056,8 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { action == nsIMIMEInfo::saveToDisk) { alwaysAsk = true; } + + nsresult rv = NS_OK; if (alwaysAsk) { // Display the dialog mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv); @@ -1982,6 +2115,14 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { return NS_OK; }
+NS_IMETHODIMP nsExternalAppHandler::CancelRequest(nsresult aReason) { + // Break our reference cycle with the download warning dialog (set up in + // OnStartRequest). + mWarningDialog = nullptr; + + return Cancel(aReason); +} + // Convert error info into proper message text and send OnStatusChange // notification to the dialog progress listener or nsITransfer implementation. void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, @@ -2668,7 +2809,7 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) { }
// Break our reference cycle with the helper app dialog (set up in - // OnStartRequest) + // ContinueRequest) mDialog = nullptr;
mRequest = nullptr; diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h index ff933451acbdf..39f00efb644b0 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.h +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -224,6 +224,7 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService, */ class nsExternalAppHandler final : public nsIStreamListener, public nsIHelperAppLauncher, + public nsIHelperAppWarningLauncher, public nsIBackgroundFileSaverObserver, public nsINamed { public: @@ -231,6 +232,7 @@ class nsExternalAppHandler final : public nsIStreamListener, NS_DECL_NSISTREAMLISTENER NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSIHELPERAPPLAUNCHER + NS_DECL_NSIHELPERAPPWARNINGLAUNCHER NS_DECL_NSICANCELABLE NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER NS_DECL_NSINAMED @@ -502,6 +504,7 @@ class nsExternalAppHandler final : public nsIStreamListener, nsCOMPtr<nsITransfer> mTransfer;
nsCOMPtr<nsIHelperAppLauncherDialog> mDialog; + nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog;
/**
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl index 657e15bc07426..ebdb1cdacf78c 100644 --- a/uriloader/exthandler/nsIExternalHelperAppService.idl +++ b/uriloader/exthandler/nsIExternalHelperAppService.idl @@ -177,3 +177,50 @@ interface nsIHelperAppLauncher : nsICancelable */ readonly attribute uint64_t browsingContextId; }; + +/** + * nsIHelperAppWarningLauncher is implemented by two classes: + * nsExternalLoadURIHandler + * nsExternalAppHandler + */ +[scriptable, uuid(cffd508b-4aaf-43ad-99c6-671d35cbc558)] +interface nsIHelperAppWarningLauncher : nsISupports +{ + /** + * Callback invoked by the external app warning dialog to continue the + * request. + * NOTE: This will release the reference to the nsIHelperAppWarningDialog. + */ + void continueRequest(); + + /** + * Callback invoked by the external app warning dialog to cancel the request. + * NOTE: This will release the reference to the nsIHelperAppWarningDialog. + * + * @param aReason + * Pass a failure code to indicate the reason why this operation is + * being canceled. It is an error to pass a success code. + */ + void cancelRequest(in nsresult aReason); +}; + +/** + * nsIHelperAppWarningDialog is implemented by Torbutton's external app + * blocker (src/components/external-app-blocker.js). + */ +[scriptable, uuid(f4899a3f-0df3-42cc-9db8-bdf599e5a208)] +interface nsIHelperAppWarningDialog : nsISupports +{ + /** + * Possibly show a launch warning dialog (it will not be shown if the user + * has chosen to not see the warning again). + * + * @param aLauncher + * A nsIHelperAppWarningLauncher to be invoked after the user confirms + * or cancels the download. + * @param aWindowContext + * The window associated with the download. + */ + void maybeShow(in nsIHelperAppWarningLauncher aLauncher, + in nsISupports aWindowContext); +};
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit f3287cf33d4ab817b59fa6e1b08c11209e8e468f Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Feb 17 12:17:25 2022 +0100
Bug 40807: Added QRCode.js to toolkit/modules --- toolkit/content/license.html | 33 ++ toolkit/modules/QRCode.jsm | 1241 ++++++++++++++++++++++++++++++++++++++++++ toolkit/modules/moz.build | 1 + 3 files changed, 1275 insertions(+)
diff --git a/toolkit/content/license.html b/toolkit/content/license.html index 782e874edf2aa..62df63156cff9 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -155,6 +155,7 @@ <li><a href="about:license#prop-types">prop-types License</a></li> <li><a href="about:license#qcms">qcms License</a></li> <li><a href="about:license#qrcode-generator">QR Code Generator License</a></li> + <li><a href="about:license#qrcode-js">QRCode.js License</a></li> <li><a href="about:license#raven-js">Raven.js License</a></li> <li><a href="about:license#react">React License</a></li> <li><a href="about:license#react-mit">React MIT License</a></li> @@ -5136,6 +5137,38 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +</pre> + + <hr> + + <h1><a id="qrcode-js"></a>QRCode.js License</h1> + + <p>This license applies to the file + <code>toolkit/modules/QRCode.jsm</code>.</p> +<pre> +The MIT License (MIT) +--------------------- +Copyright (c) 2009 Kazuhiko Arase +Copyright (c) 2012 davidshimjs +Copyright (c) 2018 ivan386 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/toolkit/modules/QRCode.jsm b/toolkit/modules/QRCode.jsm new file mode 100644 index 0000000000000..a5970ed2b7b31 --- /dev/null +++ b/toolkit/modules/QRCode.jsm @@ -0,0 +1,1241 @@ +/** + * @fileoverview + * - Using the 'QRCode for Javascript library' + * - Fixed dataset of 'QRCode for Javascript library' for support full-spec. + * - this library has no dependencies. + * + * Modified to be used as a module by the Tor Project + * + * @author Kazuhiko Arase, davidshimjs, ivan386 + * @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a> + * @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a> + */ + +var EXPORTED_SYMBOLS = ["QRCode"]; + +var QRCode; + +(function() { + //--------------------------------------------------------------------- + // QRCode for JavaScript + // + // Copyright (c) 2009 Kazuhiko Arase + // + // URL: http://www.d-project.com/ + // + // Licensed under the MIT license: + // http://www.opensource.org/licenses/mit-license.php + // + // The word "QR Code" is registered trademark of + // DENSO WAVE INCORPORATED + // http://www.denso-wave.com/qrcode/faqpatent-e.html + // + //--------------------------------------------------------------------- + function QR8bitByte(data) { + this.mode = QRMode.MODE_8BIT_BYTE; + this.data = data; + this.parsedData = []; + + // Added to support UTF-8 Characters + for (var i = 0, l = this.data.length; i < l; i++) { + var byteArray = []; + var code = this.data.charCodeAt(i); + + if (code > 0x10000) { + byteArray[0] = 0xf0 | ((code & 0x1c0000) >>> 18); + byteArray[1] = 0x80 | ((code & 0x3f000) >>> 12); + byteArray[2] = 0x80 | ((code & 0xfc0) >>> 6); + byteArray[3] = 0x80 | (code & 0x3f); + } else if (code > 0x800) { + byteArray[0] = 0xe0 | ((code & 0xf000) >>> 12); + byteArray[1] = 0x80 | ((code & 0xfc0) >>> 6); + byteArray[2] = 0x80 | (code & 0x3f); + } else if (code > 0x80) { + byteArray[0] = 0xc0 | ((code & 0x7c0) >>> 6); + byteArray[1] = 0x80 | (code & 0x3f); + } else { + byteArray[0] = code; + } + + this.parsedData.push(byteArray); + } + + this.parsedData = Array.prototype.concat.apply([], this.parsedData); + + if (this.parsedData.length != this.data.length) { + this.parsedData.unshift(191); + this.parsedData.unshift(187); + this.parsedData.unshift(239); + } + } + + QR8bitByte.prototype = { + getLength(buffer) { + return this.parsedData.length; + }, + write(buffer) { + for (var i = 0, l = this.parsedData.length; i < l; i++) { + buffer.put(this.parsedData[i], 8); + } + }, + }; + + function QRCodeModel(typeNumber, errorCorrectLevel) { + this.typeNumber = typeNumber; + this.errorCorrectLevel = errorCorrectLevel; + this.modules = null; + this.moduleCount = 0; + this.dataCache = null; + this.dataList = []; + } + + QRCodeModel.prototype = { + addData(data) { + var newData = new QR8bitByte(data); + this.dataList.push(newData); + this.dataCache = null; + }, + isDark(row, col) { + if ( + row < 0 || + this.moduleCount <= row || + col < 0 || + this.moduleCount <= col + ) { + throw new Error(row + "," + col); + } + return this.modules[row][col]; + }, + getModuleCount() { + return this.moduleCount; + }, + make() { + this.makeImpl(false, this.getBestMaskPattern()); + }, + makeImpl(test, maskPattern) { + this.moduleCount = this.typeNumber * 4 + 17; + this.modules = new Array(this.moduleCount); + for (var row = 0; row < this.moduleCount; row++) { + this.modules[row] = new Array(this.moduleCount); + for (var col = 0; col < this.moduleCount; col++) { + this.modules[row][col] = null; + } + } + this.setupPositionProbePattern(0, 0); + this.setupPositionProbePattern(this.moduleCount - 7, 0); + this.setupPositionProbePattern(0, this.moduleCount - 7); + this.setupPositionAdjustPattern(); + this.setupTimingPattern(); + this.setupTypeInfo(test, maskPattern); + if (this.typeNumber >= 7) { + this.setupTypeNumber(test); + } + if (this.dataCache == null) { + this.dataCache = QRCodeModel.createData( + this.typeNumber, + this.errorCorrectLevel, + this.dataList + ); + } + this.mapData(this.dataCache, maskPattern); + }, + setupPositionProbePattern(row, col) { + for (var r = -1; r <= 7; r++) { + if (row + r <= -1 || this.moduleCount <= row + r) { + continue; + } + for (var c = -1; c <= 7; c++) { + if (col + c <= -1 || this.moduleCount <= col + c) { + continue; + } + if ( + (0 <= r && r <= 6 && (c == 0 || c == 6)) || + (0 <= c && c <= 6 && (r == 0 || r == 6)) || + (2 <= r && r <= 4 && 2 <= c && c <= 4) + ) { + this.modules[row + r][col + c] = true; + } else { + this.modules[row + r][col + c] = false; + } + } + } + }, + getBestMaskPattern() { + var minLostPoint = 0; + var pattern = 0; + for (var i = 0; i < 8; i++) { + this.makeImpl(true, i); + var lostPoint = QRUtil.getLostPoint(this); + if (i == 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } + return pattern; + }, + createMovieClip(target_mc, instance_name, depth) { + var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth); + var cs = 1; + this.make(); + for (var row = 0; row < this.modules.length; row++) { + var y = row * cs; + for (var col = 0; col < this.modules[row].length; col++) { + var x = col * cs; + var dark = this.modules[row][col]; + if (dark) { + qr_mc.beginFill(0, 100); + qr_mc.moveTo(x, y); + qr_mc.lineTo(x + cs, y); + qr_mc.lineTo(x + cs, y + cs); + qr_mc.lineTo(x, y + cs); + qr_mc.endFill(); + } + } + } + return qr_mc; + }, + setupTimingPattern() { + for (var r = 8; r < this.moduleCount - 8; r++) { + if (this.modules[r][6] != null) { + continue; + } + this.modules[r][6] = r % 2 == 0; + } + for (var c = 8; c < this.moduleCount - 8; c++) { + if (this.modules[6][c] != null) { + continue; + } + this.modules[6][c] = c % 2 == 0; + } + }, + setupPositionAdjustPattern() { + var pos = QRUtil.getPatternPosition(this.typeNumber); + for (var i = 0; i < pos.length; i++) { + for (var j = 0; j < pos.length; j++) { + var row = pos[i]; + var col = pos[j]; + if (this.modules[row][col] != null) { + continue; + } + for (var r = -2; r <= 2; r++) { + for (var c = -2; c <= 2; c++) { + if ( + r == -2 || + r == 2 || + c == -2 || + c == 2 || + (r == 0 && c == 0) + ) { + this.modules[row + r][col + c] = true; + } else { + this.modules[row + r][col + c] = false; + } + } + } + } + } + }, + setupTypeNumber(test) { + var bits = QRUtil.getBCHTypeNumber(this.typeNumber); + for (var i = 0; i < 18; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + this.modules[Math.floor(i / 3)][ + (i % 3) + this.moduleCount - 8 - 3 + ] = mod; + } + for (var i = 0; i < 18; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + this.modules[(i % 3) + this.moduleCount - 8 - 3][ + Math.floor(i / 3) + ] = mod; + } + }, + setupTypeInfo(test, maskPattern) { + var data = (this.errorCorrectLevel << 3) | maskPattern; + var bits = QRUtil.getBCHTypeInfo(data); + for (var i = 0; i < 15; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + if (i < 6) { + this.modules[i][8] = mod; + } else if (i < 8) { + this.modules[i + 1][8] = mod; + } else { + this.modules[this.moduleCount - 15 + i][8] = mod; + } + } + for (var i = 0; i < 15; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + if (i < 8) { + this.modules[8][this.moduleCount - i - 1] = mod; + } else if (i < 9) { + this.modules[8][15 - i - 1 + 1] = mod; + } else { + this.modules[8][15 - i - 1] = mod; + } + } + this.modules[this.moduleCount - 8][8] = !test; + }, + mapData(data, maskPattern) { + var inc = -1; + var row = this.moduleCount - 1; + var bitIndex = 7; + var byteIndex = 0; + for (var col = this.moduleCount - 1; col > 0; col -= 2) { + if (col == 6) { + col--; + } + while (true) { + for (var c = 0; c < 2; c++) { + if (this.modules[row][col - c] == null) { + var dark = false; + if (byteIndex < data.length) { + dark = ((data[byteIndex] >>> bitIndex) & 1) == 1; + } + var mask = QRUtil.getMask(maskPattern, row, col - c); + if (mask) { + dark = !dark; + } + this.modules[row][col - c] = dark; + bitIndex--; + if (bitIndex == -1) { + byteIndex++; + bitIndex = 7; + } + } + } + row += inc; + if (row < 0 || this.moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + }, + }; + QRCodeModel.PAD0 = 0xec; + QRCodeModel.PAD1 = 0x11; + QRCodeModel.createData = function(typeNumber, errorCorrectLevel, dataList) { + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); + var buffer = new QRBitBuffer(); + for (var i = 0; i < dataList.length; i++) { + var data = dataList[i]; + buffer.put(data.mode, 4); + buffer.put( + data.getLength(), + QRUtil.getLengthInBits(data.mode, typeNumber) + ); + data.write(buffer); + } + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalDataCount += rsBlocks[i].dataCount; + } + if (buffer.getLengthInBits() > totalDataCount * 8) { + throw new Error( + "code length overflow. (" + + buffer.getLengthInBits() + + ">" + + totalDataCount * 8 + + ")" + ); + } + if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { + buffer.put(0, 4); + } + while (buffer.getLengthInBits() % 8 != 0) { + buffer.putBit(false); + } + while (true) { + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(QRCodeModel.PAD0, 8); + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(QRCodeModel.PAD1, 8); + } + return QRCodeModel.createBytes(buffer, rsBlocks); + }; + QRCodeModel.createBytes = function(buffer, rsBlocks) { + var offset = 0; + var maxDcCount = 0; + var maxEcCount = 0; + var dcdata = new Array(rsBlocks.length); + var ecdata = new Array(rsBlocks.length); + for (var r = 0; r < rsBlocks.length; r++) { + var dcCount = rsBlocks[r].dataCount; + var ecCount = rsBlocks[r].totalCount - dcCount; + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); + dcdata[r] = new Array(dcCount); + for (var i = 0; i < dcdata[r].length; i++) { + dcdata[r][i] = 0xff & buffer.buffer[i + offset]; + } + offset += dcCount; + var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); + var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1); + var modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.getLength() - 1); + for (var i = 0; i < ecdata[r].length; i++) { + var modIndex = i + modPoly.getLength() - ecdata[r].length; + ecdata[r][i] = modIndex >= 0 ? modPoly.get(modIndex) : 0; + } + } + var totalCodeCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalCodeCount += rsBlocks[i].totalCount; + } + var data = new Array(totalCodeCount); + var index = 0; + for (var i = 0; i < maxDcCount; i++) { + for (var r = 0; r < rsBlocks.length; r++) { + if (i < dcdata[r].length) { + data[index++] = dcdata[r][i]; + } + } + } + for (var i = 0; i < maxEcCount; i++) { + for (var r = 0; r < rsBlocks.length; r++) { + if (i < ecdata[r].length) { + data[index++] = ecdata[r][i]; + } + } + } + return data; + }; + var QRMode = { + MODE_NUMBER: 1 << 0, + MODE_ALPHA_NUM: 1 << 1, + MODE_8BIT_BYTE: 1 << 2, + MODE_KANJI: 1 << 3, + }; + var QRErrorCorrectLevel = { L: 1, M: 0, Q: 3, H: 2 }; + var QRMaskPattern = { + PATTERN000: 0, + PATTERN001: 1, + PATTERN010: 2, + PATTERN011: 3, + PATTERN100: 4, + PATTERN101: 5, + PATTERN110: 6, + PATTERN111: 7, + }; + var QRUtil = { + PATTERN_POSITION_TABLE: [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], + ], + G15: + (1 << 10) | + (1 << 8) | + (1 << 5) | + (1 << 4) | + (1 << 2) | + (1 << 1) | + (1 << 0), + G18: + (1 << 12) | + (1 << 11) | + (1 << 10) | + (1 << 9) | + (1 << 8) | + (1 << 5) | + (1 << 2) | + (1 << 0), + G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1), + getBCHTypeInfo(data) { + var d = data << 10; + while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) { + d ^= + QRUtil.G15 << + (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)); + } + return ((data << 10) | d) ^ QRUtil.G15_MASK; + }, + getBCHTypeNumber(data) { + var d = data << 12; + while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) { + d ^= + QRUtil.G18 << + (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)); + } + return (data << 12) | d; + }, + getBCHDigit(data) { + var digit = 0; + while (data != 0) { + digit++; + data >>>= 1; + } + return digit; + }, + getPatternPosition(typeNumber) { + return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]; + }, + getMask(maskPattern, i, j) { + switch (maskPattern) { + case QRMaskPattern.PATTERN000: + return (i + j) % 2 == 0; + case QRMaskPattern.PATTERN001: + return i % 2 == 0; + case QRMaskPattern.PATTERN010: + return j % 3 == 0; + case QRMaskPattern.PATTERN011: + return (i + j) % 3 == 0; + case QRMaskPattern.PATTERN100: + return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; + case QRMaskPattern.PATTERN101: + return ((i * j) % 2) + ((i * j) % 3) == 0; + case QRMaskPattern.PATTERN110: + return (((i * j) % 2) + ((i * j) % 3)) % 2 == 0; + case QRMaskPattern.PATTERN111: + return (((i * j) % 3) + ((i + j) % 2)) % 2 == 0; + default: + throw new Error("bad maskPattern:" + maskPattern); + } + }, + getErrorCorrectPolynomial(errorCorrectLength) { + var a = new QRPolynomial([1], 0); + for (var i = 0; i < errorCorrectLength; i++) { + a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); + } + return a; + }, + getLengthInBits(mode, type) { + if (1 <= type && type < 10) { + switch (mode) { + case QRMode.MODE_NUMBER: + return 10; + case QRMode.MODE_ALPHA_NUM: + return 9; + case QRMode.MODE_8BIT_BYTE: + return 8; + case QRMode.MODE_KANJI: + return 8; + default: + throw new Error("mode:" + mode); + } + } else if (type < 27) { + switch (mode) { + case QRMode.MODE_NUMBER: + return 12; + case QRMode.MODE_ALPHA_NUM: + return 11; + case QRMode.MODE_8BIT_BYTE: + return 16; + case QRMode.MODE_KANJI: + return 10; + default: + throw new Error("mode:" + mode); + } + } else if (type < 41) { + switch (mode) { + case QRMode.MODE_NUMBER: + return 14; + case QRMode.MODE_ALPHA_NUM: + return 13; + case QRMode.MODE_8BIT_BYTE: + return 16; + case QRMode.MODE_KANJI: + return 12; + default: + throw new Error("mode:" + mode); + } + } else { + throw new Error("type:" + type); + } + }, + getLostPoint(qrCode) { + var moduleCount = qrCode.getModuleCount(); + var lostPoint = 0; + for (var row = 0; row < moduleCount; row++) { + for (var col = 0; col < moduleCount; col++) { + var sameCount = 0; + var dark = qrCode.isDark(row, col); + for (var r = -1; r <= 1; r++) { + if (row + r < 0 || moduleCount <= row + r) { + continue; + } + for (var c = -1; c <= 1; c++) { + if (col + c < 0 || moduleCount <= col + c) { + continue; + } + if (r == 0 && c == 0) { + continue; + } + if (dark == qrCode.isDark(row + r, col + c)) { + sameCount++; + } + } + } + if (sameCount > 5) { + lostPoint += 3 + sameCount - 5; + } + } + } + for (var row = 0; row < moduleCount - 1; row++) { + for (var col = 0; col < moduleCount - 1; col++) { + var count = 0; + if (qrCode.isDark(row, col)) { + count++; + } + if (qrCode.isDark(row + 1, col)) { + count++; + } + if (qrCode.isDark(row, col + 1)) { + count++; + } + if (qrCode.isDark(row + 1, col + 1)) { + count++; + } + if (count == 0 || count == 4) { + lostPoint += 3; + } + } + } + for (var row = 0; row < moduleCount; row++) { + for (var col = 0; col < moduleCount - 6; col++) { + if ( + qrCode.isDark(row, col) && + !qrCode.isDark(row, col + 1) && + qrCode.isDark(row, col + 2) && + qrCode.isDark(row, col + 3) && + qrCode.isDark(row, col + 4) && + !qrCode.isDark(row, col + 5) && + qrCode.isDark(row, col + 6) + ) { + lostPoint += 40; + } + } + } + for (var col = 0; col < moduleCount; col++) { + for (var row = 0; row < moduleCount - 6; row++) { + if ( + qrCode.isDark(row, col) && + !qrCode.isDark(row + 1, col) && + qrCode.isDark(row + 2, col) && + qrCode.isDark(row + 3, col) && + qrCode.isDark(row + 4, col) && + !qrCode.isDark(row + 5, col) && + qrCode.isDark(row + 6, col) + ) { + lostPoint += 40; + } + } + } + var darkCount = 0; + for (var col = 0; col < moduleCount; col++) { + for (var row = 0; row < moduleCount; row++) { + if (qrCode.isDark(row, col)) { + darkCount++; + } + } + } + var ratio = + Math.abs((100 * darkCount) / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; + return lostPoint; + }, + }; + var QRMath = { + glog(n) { + if (n < 1) { + throw new Error("glog(" + n + ")"); + } + return QRMath.LOG_TABLE[n]; + }, + gexp(n) { + while (n < 0) { + n += 255; + } + while (n >= 256) { + n -= 255; + } + return QRMath.EXP_TABLE[n]; + }, + EXP_TABLE: new Array(256), + LOG_TABLE: new Array(256), + }; + for (var i = 0; i < 8; i++) { + QRMath.EXP_TABLE[i] = 1 << i; + } + for (var i = 8; i < 256; i++) { + QRMath.EXP_TABLE[i] = + QRMath.EXP_TABLE[i - 4] ^ + QRMath.EXP_TABLE[i - 5] ^ + QRMath.EXP_TABLE[i - 6] ^ + QRMath.EXP_TABLE[i - 8]; + } + for (var i = 0; i < 255; i++) { + QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i; + } + function QRPolynomial(num, shift) { + if (num.length == undefined) { + throw new Error(num.length + "/" + shift); + } + var offset = 0; + while (offset < num.length && num[offset] == 0) { + offset++; + } + this.num = new Array(num.length - offset + shift); + for (var i = 0; i < num.length - offset; i++) { + this.num[i] = num[i + offset]; + } + } + QRPolynomial.prototype = { + get(index) { + return this.num[index]; + }, + getLength() { + return this.num.length; + }, + multiply(e) { + var num = new Array(this.getLength() + e.getLength() - 1); + for (var i = 0; i < this.getLength(); i++) { + for (var j = 0; j < e.getLength(); j++) { + num[i + j] ^= QRMath.gexp( + QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)) + ); + } + } + return new QRPolynomial(num, 0); + }, + mod(e) { + if (this.getLength() - e.getLength() < 0) { + return this; + } + var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); + var num = new Array(this.getLength()); + for (var i = 0; i < this.getLength(); i++) { + num[i] = this.get(i); + } + for (var i = 0; i < e.getLength(); i++) { + num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio); + } + return new QRPolynomial(num, 0).mod(e); + }, + }; + function QRRSBlock(totalCount, dataCount) { + this.totalCount = totalCount; + this.dataCount = dataCount; + } + QRRSBlock.RS_BLOCK_TABLE = [ + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12], + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16], + ]; + QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) { + var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); + if (rsBlock == undefined) { + throw new Error( + "bad rs block @ typeNumber:" + + typeNumber + + "/errorCorrectLevel:" + + errorCorrectLevel + ); + } + var length = rsBlock.length / 3; + var list = []; + for (var i = 0; i < length; i++) { + var count = rsBlock[i * 3 + 0]; + var totalCount = rsBlock[i * 3 + 1]; + var dataCount = rsBlock[i * 3 + 2]; + for (var j = 0; j < count; j++) { + list.push(new QRRSBlock(totalCount, dataCount)); + } + } + return list; + }; + QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) { + switch (errorCorrectLevel) { + case QRErrorCorrectLevel.L: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + case QRErrorCorrectLevel.M: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + case QRErrorCorrectLevel.Q: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + case QRErrorCorrectLevel.H: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + default: + return undefined; + } + }; + function QRBitBuffer() { + this.buffer = []; + this.length = 0; + } + QRBitBuffer.prototype = { + get(index) { + var bufIndex = Math.floor(index / 8); + return ((this.buffer[bufIndex] >>> (7 - (index % 8))) & 1) == 1; + }, + put(num, length) { + for (var i = 0; i < length; i++) { + this.putBit(((num >>> (length - i - 1)) & 1) == 1); + } + }, + getLengthInBits() { + return this.length; + }, + putBit(bit) { + var bufIndex = Math.floor(this.length / 8); + if (this.buffer.length <= bufIndex) { + this.buffer.push(0); + } + if (bit) { + this.buffer[bufIndex] |= 0x80 >>> this.length % 8; + } + this.length++; + }, + }; + var QRCodeLimitLength = [ + [17, 14, 11, 7], + [32, 26, 20, 14], + [53, 42, 32, 24], + [78, 62, 46, 34], + [106, 84, 60, 44], + [134, 106, 74, 58], + [154, 122, 86, 64], + [192, 152, 108, 84], + [230, 180, 130, 98], + [271, 213, 151, 119], + [321, 251, 177, 137], + [367, 287, 203, 155], + [425, 331, 241, 177], + [458, 362, 258, 194], + [520, 412, 292, 220], + [586, 450, 322, 250], + [644, 504, 364, 280], + [718, 560, 394, 310], + [792, 624, 442, 338], + [858, 666, 482, 382], + [929, 711, 509, 403], + [1003, 779, 565, 439], + [1091, 857, 611, 461], + [1171, 911, 661, 511], + [1273, 997, 715, 535], + [1367, 1059, 751, 593], + [1465, 1125, 805, 625], + [1528, 1190, 868, 658], + [1628, 1264, 908, 698], + [1732, 1370, 982, 742], + [1840, 1452, 1030, 790], + [1952, 1538, 1112, 842], + [2068, 1628, 1168, 898], + [2188, 1722, 1228, 958], + [2303, 1809, 1283, 983], + [2431, 1911, 1351, 1051], + [2563, 1989, 1423, 1093], + [2699, 2099, 1499, 1139], + [2809, 2213, 1579, 1219], + [2953, 2331, 1663, 1273], + ]; + + var svgDrawer = (function() { + var Drawing = function(el, htOption) { + this._el = el; + this._htOption = htOption; + }; + + Drawing.prototype.draw = function(oQRCode) { + var _htOption = this._htOption; + var _el = this._el; + var nCount = oQRCode.getModuleCount(); + + this.clear(); + + function makeSVG(tag, attrs) { + var el = _el.ownerDocument.createElementNS( + "http://www.w3.org/2000/svg", + tag + ); + for (var k in attrs) { + if (attrs.hasOwnProperty(k)) { + el.setAttribute(k, attrs[k]); + } + } + return el; + } + + var svg = makeSVG("svg", { + viewBox: "0 0 " + String(nCount) + " " + String(nCount), + width: "100%", + height: "100%", + fill: _htOption.colorLight, + "shape-rendering": "crispEdges", + }); + svg.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns", + "http://www.w3.org/2000/svg" + ); + _el.appendChild(svg); + + svg.appendChild( + makeSVG("rect", { + fill: _htOption.colorLight, + width: "100%", + height: "100%", + }) + ); + + var path = []; + for (var row = 0; row < nCount; row++) { + for (var col = 0; col < nCount; col++) { + var width = 0; + while (col + width < nCount && oQRCode.isDark(row, col + width)) { + width++; + } + + if (width > 0) { + path.push("M" + col + " " + row + "v1h" + width + "v-1z"); + col += width; + } + } + } + var child = makeSVG("path", { + d: path.join(""), + fill: _htOption.colorDark, + }); + svg.appendChild(child); + }; + Drawing.prototype.clear = function() { + while (this._el.hasChildNodes()) { + this._el.removeChild(this._el.lastChild); + } + }; + return Drawing; + })(); + + /** + * Get the type by string length + * + * @private + * @param {String} sText + * @param {Number} nCorrectLevel + * @return {Number} type + */ + function _getTypeNumber(sText, nCorrectLevel) { + var nType = 1; + var length = _getUTF8Length(sText); + + for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) { + var nLimit = 0; + + switch (nCorrectLevel) { + case QRErrorCorrectLevel.L: + nLimit = QRCodeLimitLength[i][0]; + break; + case QRErrorCorrectLevel.M: + nLimit = QRCodeLimitLength[i][1]; + break; + case QRErrorCorrectLevel.Q: + nLimit = QRCodeLimitLength[i][2]; + break; + case QRErrorCorrectLevel.H: + nLimit = QRCodeLimitLength[i][3]; + break; + } + + if (length <= nLimit) { + break; + } else { + nType++; + } + } + + if (nType > QRCodeLimitLength.length) { + throw new Error("Too long data"); + } + + return nType; + } + + function _getUTF8Length(sText) { + var replacedText = encodeURI(sText) + .toString() + .replace(/%[0-9a-fA-F]{2}/g, "a"); + return replacedText.length + (replacedText.length != sText ? 3 : 0); + } + + /** + * @class QRCode + * @constructor + * @example + * new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie"); + * + * @example + * var oQRCode = new QRCode("test", { + * text : "http://naver.com", + * width : 128, + * height : 128 + * }); + * + * oQRCode.clear(); // Clear the QRCode. + * oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode. + * + * @param {HTMLElement} el target element + * @param {Object|String} vOption + * @param {String} vOption.text QRCode link data + * @param {Number} [vOption.width=256] + * @param {Number} [vOption.height=256] + * @param {String} [vOption.colorDark="#000000"] + * @param {String} [vOption.colorLight="#ffffff"] + * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H] + */ + QRCode = function(el, vOption) { + this._htOption = { + width: 256, + height: 256, + typeNumber: 4, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRErrorCorrectLevel.H, + }; + + if (typeof vOption === "string") { + vOption = { + text: vOption, + }; + } + + // Overwrites options + if (vOption) { + for (var i in vOption) { + this._htOption[i] = vOption[i]; + } + } + + this._el = el; + this._oQRCode = null; + this._oDrawing = new svgDrawer(this._el, this._htOption); + + if (this._htOption.text) { + this.makeCode(this._htOption.text); + } + }; + + /** + * Make the QRCode + * + * @param {String} sText link data + */ + QRCode.prototype.makeCode = function(sText) { + this._oQRCode = new QRCodeModel( + _getTypeNumber(sText, this._htOption.correctLevel), + this._htOption.correctLevel + ); + this._oQRCode.addData(sText); + this._oQRCode.make(); + this._oDrawing.draw(this._oQRCode); + }; + + /** + * Clear the QRCode + */ + QRCode.prototype.clear = function() { + this._oDrawing.clear(); + }; + + /** + * @name QRCode.CorrectLevel + */ + QRCode.CorrectLevel = QRErrorCorrectLevel; +})(); diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index e77c33869b621..5523a8975a898 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -202,6 +202,7 @@ EXTRA_JS_MODULES += [ "Promise-backend.js", "Promise.jsm", "PromiseUtils.jsm", + "QRCode.jsm", "Region.jsm", "RemotePageAccessManager.jsm", "ResetProfile.jsm",
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 4a4cd91e58664b7f4217f870b2a703e2b6d9187d Author: Richard Pospesel richard@torproject.org AuthorDate: Mon Sep 16 15:25:39 2019 -0700
Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
This patch adds a new about:preferences#connection page which allows modifying bridge, proxy, and firewall settings from within Tor Browser. All of the functionality present in tor-launcher's Network Configuration panel is present:
- Setting built-in bridges - Requesting bridges from BridgeDB via moat - Using user-provided bridges - Configuring SOCKS4, SOCKS5, and HTTP/HTTPS proxies - Setting firewall ports - Viewing and Copying Tor's logs - The Networking Settings in General preferences has been removed
Bug 40774: Update about:preferences page to match new UI designs --- browser/components/moz.build | 1 + browser/components/preferences/main.inc.xhtml | 54 - browser/components/preferences/main.js | 14 - browser/components/preferences/preferences.js | 9 + browser/components/preferences/preferences.xhtml | 6 + .../torpreferences/content/bridgeQrDialog.jsm | 51 + .../torpreferences/content/bridgeQrDialog.xhtml | 23 + .../torpreferences/content/builtinBridgeDialog.jsm | 142 +++ .../content/builtinBridgeDialog.xhtml | 43 + .../content/connectionCategory.inc.xhtml | 9 + .../torpreferences/content/connectionPane.js | 1315 ++++++++++++++++++++ .../torpreferences/content/connectionPane.xhtml | 177 +++ .../content/connectionSettingsDialog.jsm | 393 ++++++ .../content/connectionSettingsDialog.xhtml | 62 + .../components/torpreferences/content/network.svg | 6 + .../torpreferences/content/provideBridgeDialog.jsm | 69 + .../content/provideBridgeDialog.xhtml | 21 + .../torpreferences/content/requestBridgeDialog.jsm | 211 ++++ .../content/requestBridgeDialog.xhtml | 35 + .../torpreferences/content/torLogDialog.jsm | 84 ++ .../torpreferences/content/torLogDialog.xhtml | 23 + .../torpreferences/content/torPreferences.css | 541 ++++++++ .../torpreferences/content/torPreferencesIcon.svg | 8 + browser/components/torpreferences/jar.mn | 19 + browser/components/torpreferences/moz.build | 1 + 25 files changed, 3249 insertions(+), 68 deletions(-)
diff --git a/browser/components/moz.build b/browser/components/moz.build index 1bc09f4093fb7..66de87290bd83 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -53,6 +53,7 @@ DIRS += [ "syncedtabs", "uitour", "urlbar", + "torpreferences", "translation", ]
diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml index a89b89f723a80..594711e614747 100644 --- a/browser/components/preferences/main.inc.xhtml +++ b/browser/components/preferences/main.inc.xhtml @@ -671,58 +671,4 @@ <label id="cfrFeaturesLearnMore" class="learnMore" data-l10n-id="browsing-cfr-recommendations-learn-more" is="text-link"/> </hbox> </groupbox> - -<hbox id="networkProxyCategory" - class="subcategory" - hidden="true" - data-category="paneGeneral"> - <html:h1 data-l10n-id="network-settings-title"/> -</hbox> - -<!-- Network Settings--> -<groupbox id="connectionGroup" data-category="paneGeneral" hidden="true"> - <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label> - - <hbox align="center"> - <hbox align="center" flex="1"> - <description id="connectionSettingsDescription" control="connectionSettings"/> - <spacer width="5"/> - <label id="connectionSettingsLearnMore" class="learnMore" is="text-link" - data-l10n-id="network-proxy-connection-learn-more"> - </label> - <separator orient="vertical"/> - </hbox> - - <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. --> - <hbox> - <button id="connectionSettings" - is="highlightable-button" - class="accessory-button" - data-l10n-id="network-proxy-connection-settings" - searchkeywords="doh trr" - search-l10n-ids=" - connection-window.title, - connection-proxy-option-no.label, - connection-proxy-option-auto.label, - connection-proxy-option-system.label, - connection-proxy-option-manual.label, - connection-proxy-http, - connection-proxy-https, - connection-proxy-http-port, - connection-proxy-socks, - connection-proxy-socks4, - connection-proxy-socks5, - connection-proxy-noproxy, - connection-proxy-noproxy-desc, - connection-proxy-https-sharing.label, - connection-proxy-autotype.label, - connection-proxy-reload.label, - connection-proxy-autologin.label, - connection-proxy-socks-remote-dns.label, - connection-dns-over-https.label, - connection-dns-over-https-url-custom.label, - " /> - </hbox> - </hbox> -</groupbox> </html:template> diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js index 2a6ba4a3d8e42..501ba9144a314 100644 --- a/browser/components/preferences/main.js +++ b/browser/components/preferences/main.js @@ -368,15 +368,6 @@ var gMainPane = { }); this.updatePerformanceSettingsBox({ duringChangeEvent: false }); this.displayUseSystemLocale(); - let connectionSettingsLink = document.getElementById( - "connectionSettingsLearnMore" - ); - let connectionSettingsUrl = - Services.urlFormatter.formatURLPref("app.support.baseURL") + - "prefs-connection-settings"; - connectionSettingsLink.setAttribute("href", connectionSettingsUrl); - this.updateProxySettingsUI(); - initializeProxyUI(gMainPane);
if (Services.prefs.getBoolPref("intl.multilingual.enabled")) { gMainPane.initBrowserLocale(); @@ -510,11 +501,6 @@ var gMainPane = { "change", gMainPane.updateHardwareAcceleration.bind(gMainPane) ); - setEventListener( - "connectionSettings", - "command", - gMainPane.showConnections - ); setEventListener( "browserContainersCheckbox", "command", diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js index a3656f827ffc6..5981bcd38fc83 100644 --- a/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js @@ -13,6 +13,7 @@ /* import-globals-from findInPage.js */ /* import-globals-from ../../base/content/utilityOverlay.js */ /* import-globals-from ../../../toolkit/content/preferencesBindings.js */ +/* import-globals-from ../torpreferences/content/connectionPane.js */
"use strict";
@@ -136,6 +137,14 @@ function init_all() { register_module("paneSync", gSyncPane); } register_module("paneSearchResults", gSearchResultsPane); + if (gConnectionPane.enabled) { + document.getElementById("category-connection").hidden = false; + register_module("paneConnection", gConnectionPane); + } else { + // Remove the pane from the DOM so it doesn't get incorrectly included in search results. + document.getElementById("template-paneConnection").remove(); + } + gSearchResultsPane.init(); gMainPane.preInit();
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 32184867ac179..9ee09581de32a 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -6,12 +6,14 @@ <?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://global/skin/in-content/common.css"?> +<?xml-stylesheet href="chrome://global/skin/in-content/toggle-button.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> <?xml-stylesheet href="chrome://browser/content/preferences/dialogs/handlers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [ <!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd"> @@ -154,6 +156,9 @@ <image class="category-icon"/> <label class="category-name" flex="1" data-l10n-id="pane-experimental-title"></label> </richlistitem> + +#include ../torpreferences/content/connectionCategory.inc.xhtml + </richlistbox>
<spacer flex="1"/> @@ -207,6 +212,7 @@ #include containers.inc.xhtml #include sync.inc.xhtml #include experimental.inc.xhtml +#include ../torpreferences/content/connectionPane.xhtml </vbox> </vbox> </vbox> diff --git a/browser/components/torpreferences/content/bridgeQrDialog.jsm b/browser/components/torpreferences/content/bridgeQrDialog.jsm new file mode 100644 index 0000000000000..e63347742ea50 --- /dev/null +++ b/browser/components/torpreferences/content/bridgeQrDialog.jsm @@ -0,0 +1,51 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["BridgeQrDialog"]; + +const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm"); + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +class BridgeQrDialog { + constructor() { + this._bridgeString = ""; + } + + static get selectors() { + return { + target: "#bridgeQr-target", + }; + } + + _populateXUL(window, dialog) { + dialog.parentElement.setAttribute("title", TorStrings.settings.scanQrTitle); + const target = dialog.querySelector(BridgeQrDialog.selectors.target); + const style = window.getComputedStyle(target); + const width = style.width.substr(0, style.width.length - 2); + const height = style.height.substr(0, style.height.length - 2); + new QRCode(target, { + text: this._bridgeString, + width, + height, + colorDark: style.color, + colorLight: style.backgroundColor, + document: window.document, + }); + } + + init(window, dialog) { + // Defer to later until Firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, dialog); + }, 0); + } + + openDialog(gSubDialog, bridgeString) { + this._bridgeString = bridgeString; + gSubDialog.open( + "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/bridgeQrDialog.xhtml b/browser/components/torpreferences/content/bridgeQrDialog.xhtml new file mode 100644 index 0000000000000..2a49e4c0e7d9e --- /dev/null +++ b/browser/components/torpreferences/content/bridgeQrDialog.xhtml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="bridgeQr-dialog" buttons="accept"> + <html:div id="bridgeQr-container"> + <html:div id="bridgeQr-target"/> + <html:div id="bridgeQr-onionBox"/> + <html:div id="bridgeQr-onion"/> + </html:div> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let dialogObject = window.arguments[0]; + let dialogElement = document.getElementById("bridgeQr-dialog"); + dialogObject.init(window, dialogElement); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.jsm b/browser/components/torpreferences/content/builtinBridgeDialog.jsm new file mode 100644 index 0000000000000..1d4dda8f5ca9c --- /dev/null +++ b/browser/components/torpreferences/content/builtinBridgeDialog.jsm @@ -0,0 +1,142 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["BuiltinBridgeDialog"]; + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +const { + TorSettings, + TorBridgeSource, + TorBuiltinBridgeTypes, +} = ChromeUtils.import("resource:///modules/TorSettings.jsm"); + +class BuiltinBridgeDialog { + constructor() { + this._dialog = null; + this._bridgeType = ""; + this._windowPadding = 0; + } + + static get selectors() { + return { + header: "#torPreferences-builtinBridge-header", + description: "#torPreferences-builtinBridge-description", + radiogroup: "#torPreferences-builtinBridge-typeSelection", + obfsRadio: "#torPreferences-builtinBridges-radioObfs", + obfsDescr: "#torPreferences-builtinBridges-descrObfs", + snowflakeRadio: "#torPreferences-builtinBridges-radioSnowflake", + snowflakeDescr: "#torPreferences-builtinBridges-descrSnowflake", + meekAzureRadio: "#torPreferences-builtinBridges-radioMeekAzure", + meekAzureDescr: "#torPreferences-builtinBridges-descrMeekAzure", + }; + } + + _populateXUL(window, aDialog) { + const selectors = BuiltinBridgeDialog.selectors; + + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + { + dialogWin.setAttribute("title", TorStrings.settings.builtinBridgeTitle); + let windowStyle = window.getComputedStyle(dialogWin); + this._windowPadding = + parseFloat(windowStyle.paddingLeft) + + parseFloat(windowStyle.paddingRight); + } + const initialWidth = dialogWin.clientWidth - this._windowPadding; + + this._dialog.querySelector(selectors.header).textContent = + TorStrings.settings.builtinBridgeHeader; + this._dialog.querySelector(selectors.description).textContent = + TorStrings.settings.builtinBridgeDescription; + let radioGroup = this._dialog.querySelector(selectors.radiogroup); + + let types = { + obfs4: { + elemRadio: this._dialog.querySelector(selectors.obfsRadio), + elemDescr: this._dialog.querySelector(selectors.obfsDescr), + label: TorStrings.settings.builtinBridgeObfs4, + descr: TorStrings.settings.builtinBridgeObfs4Description, + }, + snowflake: { + elemRadio: this._dialog.querySelector(selectors.snowflakeRadio), + elemDescr: this._dialog.querySelector(selectors.snowflakeDescr), + label: TorStrings.settings.builtinBridgeSnowflake, + descr: TorStrings.settings.builtinBridgeSnowflakeDescription, + }, + "meek-azure": { + elemRadio: this._dialog.querySelector(selectors.meekAzureRadio), + elemDescr: this._dialog.querySelector(selectors.meekAzureDescr), + label: TorStrings.settings.builtinBridgeMeekAzure, + descr: TorStrings.settings.builtinBridgeMeekAzureDescription, + }, + }; + + TorBuiltinBridgeTypes.forEach(type => { + types[type].elemRadio.parentElement.setAttribute("hidden", "false"); + types[type].elemDescr.parentElement.setAttribute("hidden", "false"); + types[type].elemRadio.setAttribute("label", types[type].label); + types[type].elemDescr.textContent = types[type].descr; + }); + + if ( + TorSettings.bridges.enabled && + TorSettings.bridges.source == TorBridgeSource.BuiltIn + ) { + radioGroup.selectedItem = + types[TorSettings.bridges.builtin_type]?.elemRadio; + this._bridgeType = TorSettings.bridges.builtin_type; + } else { + radioGroup.selectedItem = null; + this._bridgeType = ""; + } + + // Use the initial width, because the window is expanded when we add texts + this.resized(initialWidth); + + this._dialog.addEventListener("dialogaccept", e => { + this._bridgeType = radioGroup.value; + }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + "https://tb-manual.torproject.org/circumvention/", + "tab" + ); + }); + } + + resized(width) { + if (this._dialog === null) { + return; + } + const dialogWin = this._dialog.parentElement; + if (width === undefined) { + width = dialogWin.clientWidth - this._windowPadding; + } + let windowPos = dialogWin.getBoundingClientRect(); + dialogWin.querySelectorAll("div").forEach(div => { + let divPos = div.getBoundingClientRect(); + div.style.width = width - (divPos.left - windowPos.left) + "px"; + }); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, aDialog); + }, 0); + } + + openDialog(gSubDialog, aCloseCallback) { + gSubDialog.open( + "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml", + { + features: "resizable=yes", + closingCallback: () => { + aCloseCallback(this._bridgeType); + }, + }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.xhtml b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml new file mode 100644 index 0000000000000..c1bf202ca1be5 --- /dev/null +++ b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-builtinBridge-dialog" + buttons="help,accept,cancel"> + <html:h3 id="torPreferences-builtinBridge-header">​</html:h3> + <description> + <html:div id="torPreferences-builtinBridge-description">​<br/>​</html:div> + </description> + <radiogroup id="torPreferences-builtinBridge-typeSelection"> + <hbox hidden="true"> + <radio id="torPreferences-builtinBridges-radioObfs" value="obfs4"/> + </hbox> + <hbox hidden="true" class="indent"> + <html:div id="torPreferences-builtinBridges-descrObfs"></html:div> + </hbox> + <hbox hidden="true"> + <radio id="torPreferences-builtinBridges-radioSnowflake" value="snowflake"/> + </hbox> + <hbox hidden="true" class="indent"> + <html:div id="torPreferences-builtinBridges-descrSnowflake"></html:div> + </hbox> + <hbox hidden="true"> + <radio id="torPreferences-builtinBridges-radioMeekAzure" value="meek-azure"/> + </hbox> + <hbox hidden="true" class="indent"> + <html:div id="torPreferences-builtinBridges-descrMeekAzure"></html:div> + </hbox> + </radiogroup> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let builtinBridgeDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-builtinBridge-dialog"); + builtinBridgeDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/connectionCategory.inc.xhtml b/browser/components/torpreferences/content/connectionCategory.inc.xhtml new file mode 100644 index 0000000000000..15cf24cfe6950 --- /dev/null +++ b/browser/components/torpreferences/content/connectionCategory.inc.xhtml @@ -0,0 +1,9 @@ +<richlistitem id="category-connection" + class="category" + value="paneConnection" + helpTopic="prefs-connection" + align="center" + hidden="true"> + <image class="category-icon"/> + <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Connection"/> +</richlistitem> diff --git a/browser/components/torpreferences/content/connectionPane.js b/browser/components/torpreferences/content/connectionPane.js new file mode 100644 index 0000000000000..309d6498a0c80 --- /dev/null +++ b/browser/components/torpreferences/content/connectionPane.js @@ -0,0 +1,1315 @@ +"use strict"; + +/* global Services, gSubDialog */ + +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +const { + TorSettings, + TorSettingsTopics, + TorSettingsData, + TorBridgeSource, +} = ChromeUtils.import("resource:///modules/TorSettings.jsm"); + +const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +const { + TorConnect, + TorConnectTopics, + TorConnectState, + TorCensorshipLevel, +} = ChromeUtils.import("resource:///modules/TorConnect.jsm"); + +const { TorLogDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/torLogDialog.jsm" +); + +const { ConnectionSettingsDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/connectionSettingsDialog.jsm" +); + +const { BridgeQrDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/bridgeQrDialog.jsm" +); + +const { BuiltinBridgeDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/builtinBridgeDialog.jsm" +); + +const { RequestBridgeDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/requestBridgeDialog.jsm" +); + +const { ProvideBridgeDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/provideBridgeDialog.jsm" +); + +const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm"); + +const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm"); + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +const InternetStatus = Object.freeze({ + Unknown: 0, + Online: 1, + Offline: -1, +}); + +/* + Connection Pane + + Code for populating the XUL in about:preferences#connection, handling input events, interfacing with tor-launcher +*/ +const gConnectionPane = (function() { + /* CSS selectors for all of the Tor Network DOM elements we need to access */ + const selectors = { + category: { + title: "label#torPreferences-labelCategory", + }, + messageBox: { + box: "div#torPreferences-connectMessageBox", + message: "td#torPreferences-connectMessageBox-message", + button: "button#torPreferences-connectMessageBox-button", + }, + torPreferences: { + header: "h1#torPreferences-header", + description: "span#torPreferences-description", + learnMore: "label#torPreferences-learnMore", + }, + status: { + internetLabel: "#torPreferences-status-internet-label", + internetTest: "#torPreferences-status-internet-test", + internetIcon: "#torPreferences-status-internet-statusIcon", + internetStatus: "#torPreferences-status-internet-status", + torLabel: "#torPreferences-status-tor-label", + torIcon: "#torPreferences-status-tor-statusIcon", + torStatus: "#torPreferences-status-tor-status", + }, + quickstart: { + header: "h2#torPreferences-quickstart-header", + description: "span#torPreferences-quickstart-description", + enableQuickstartCheckbox: "checkbox#torPreferences-quickstart-toggle", + }, + bridges: { + header: "h1#torPreferences-bridges-header", + description: "span#torPreferences-bridges-description", + learnMore: "label#torPreferences-bridges-learnMore", + locationGroup: "#torPreferences-bridges-locationGroup", + locationLabel: "#torPreferences-bridges-locationLabel", + location: "#torPreferences-bridges-location", + locationEntries: "#torPreferences-bridges-locationEntries", + chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe", + currentHeader: "#torPreferences-currentBridges-header", + currentHeaderText: "#torPreferences-currentBridges-headerText", + switch: "#torPreferences-currentBridges-switch", + cards: "#torPreferences-currentBridges-cards", + cardTemplate: "#torPreferences-bridgeCard-template", + card: ".torPreferences-bridgeCard", + cardId: ".torPreferences-bridgeCard-id", + cardHeadingAddr: ".torPreferences-bridgeCard-headingAddr", + cardConnectedLabel: ".torPreferences-bridgeCard-connectedLabel", + cardOptions: ".torPreferences-bridgeCard-options", + cardMenu: "#torPreferences-bridgeCard-menu", + cardQrGrid: ".torPreferences-bridgeCard-grid", + cardQrContainer: ".torPreferences-bridgeCard-qr", + cardQr: ".torPreferences-bridgeCard-qrCode", + cardShare: ".torPreferences-bridgeCard-share", + cardAddr: ".torPreferences-bridgeCard-addr", + cardLearnMore: ".torPreferences-bridgeCard-learnMore", + cardCopy: ".torPreferences-bridgeCard-copyButton", + showAll: "#torPreferences-currentBridges-showAll", + removeAll: "#torPreferences-currentBridges-removeAll", + addHeader: "#torPreferences-addBridge-header", + addBuiltinLabel: "#torPreferences-addBridge-labelBuiltinBridge", + addBuiltinButton: "#torPreferences-addBridge-buttonBuiltinBridge", + requestLabel: "#torPreferences-addBridge-labelRequestBridge", + requestButton: "#torPreferences-addBridge-buttonRequestBridge", + enterLabel: "#torPreferences-addBridge-labelEnterBridge", + enterButton: "#torPreferences-addBridge-buttonEnterBridge", + }, + advanced: { + header: "h1#torPreferences-advanced-header", + label: "#torPreferences-advanced-label", + button: "#torPreferences-advanced-button", + torLogsLabel: "label#torPreferences-torLogs", + torLogsButton: "button#torPreferences-buttonTorLogs", + }, + }; /* selectors */ + + let retval = { + // cached frequently accessed DOM elements + _enableQuickstartCheckbox: null, + + _internetStatus: InternetStatus.Unknown, + + _controller: null, + + _currentBridge: "", + + // disables the provided list of elements + _setElementsDisabled(elements, disabled) { + for (let currentElement of elements) { + currentElement.disabled = disabled; + } + }, + + // populate xul with strings and cache the relevant elements + _populateXUL() { + // saves tor settings to disk when navigate away from about:preferences + window.addEventListener("blur", val => { + TorProtocolService.flushSettings(); + }); + + document + .querySelector(selectors.category.title) + .setAttribute("value", TorStrings.settings.categoryTitle); + + let prefpane = document.getElementById("mainPrefPane"); + + // 'Connect to Tor' Message Bar + + const messageBox = prefpane.querySelector(selectors.messageBox.box); + const messageBoxMessage = prefpane.querySelector( + selectors.messageBox.message + ); + const messageBoxButton = prefpane.querySelector( + selectors.messageBox.button + ); + // wire up connect button + messageBoxButton.addEventListener("click", () => { + TorConnect.beginBootstrap(); + TorConnect.openTorConnect(); + }); + + this._populateMessagebox = () => { + if ( + TorConnect.shouldShowTorConnect && + TorConnect.state === TorConnectState.Configuring + ) { + // set messagebox style and text + if (TorProtocolService.torBootstrapErrorOccurred()) { + messageBox.parentNode.style.display = null; + messageBox.className = "error"; + messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage; + messageBoxButton.innerText = TorStrings.torConnect.tryAgain; + } else { + messageBox.parentNode.style.display = null; + messageBox.className = "warning"; + messageBoxMessage.innerText = TorStrings.torConnect.connectMessage; + messageBoxButton.innerText = TorStrings.torConnect.torConnectButton; + } + } else { + // we need to explicitly hide the groupbox, as switching between + // the tor pane and other panes will 'unhide' (via the 'hidden' + // attribute) the groupbox, offsetting all of the content down + // by the groupbox's margin (even if content is 0 height) + messageBox.parentNode.style.display = "none"; + messageBox.className = "hidden"; + messageBoxMessage.innerText = ""; + messageBoxButton.innerText = ""; + } + }; + this._populateMessagebox(); + + // Heading + prefpane.querySelector(selectors.torPreferences.header).innerText = + TorStrings.settings.categoryTitle; + prefpane.querySelector(selectors.torPreferences.description).textContent = + TorStrings.settings.torPreferencesDescription; + { + let learnMore = prefpane.querySelector( + selectors.torPreferences.learnMore + ); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute( + "href", + TorStrings.settings.learnMoreTorBrowserURL + ); + } + + // Internet and Tor status + prefpane.querySelector(selectors.status.internetLabel).textContent = + TorStrings.settings.statusInternetLabel; + prefpane.querySelector(selectors.status.torLabel).textContent = + TorStrings.settings.statusTorLabel; + const internetTest = prefpane.querySelector( + selectors.status.internetTest + ); + internetTest.setAttribute( + "label", + TorStrings.settings.statusInternetTest + ); + internetTest.addEventListener("command", async () => { + this.onInternetTest(); + }); + const internetIcon = prefpane.querySelector( + selectors.status.internetIcon + ); + const internetStatus = prefpane.querySelector( + selectors.status.internetStatus + ); + const torIcon = prefpane.querySelector(selectors.status.torIcon); + const torStatus = prefpane.querySelector(selectors.status.torStatus); + this._populateStatus = () => { + switch (this._internetStatus) { + case InternetStatus.Unknown: + internetTest.removeAttribute("hidden"); + break; + case InternetStatus.Online: + internetTest.setAttribute("hidden", "true"); + internetIcon.className = "online"; + internetStatus.textContent = + TorStrings.settings.statusInternetOnline; + break; + case InternetStatus.Offline: + internetTest.setAttribute("hidden", "true"); + internetIcon.className = "offline"; + internetStatus.textContent = + TorStrings.settings.statusInternetOffline; + break; + } + if (TorConnect.state === TorConnectState.Bootstrapped) { + torIcon.className = "connected"; + torStatus.textContent = TorStrings.settings.statusTorConnected; + } else if ( + TorConnect.detectedCensorshipLevel > TorCensorshipLevel.None + ) { + torIcon.className = "blocked"; + torStatus.textContent = TorStrings.settings.statusTorBlocked; + } else { + torIcon.className = ""; + torStatus.textContent = TorStrings.settings.statusTorNotConnected; + } + }; + this._populateStatus(); + + // Quickstart + prefpane.querySelector(selectors.quickstart.header).innerText = + TorStrings.settings.quickstartHeading; + prefpane.querySelector(selectors.quickstart.description).textContent = + TorStrings.settings.quickstartDescription; + + this._enableQuickstartCheckbox = prefpane.querySelector( + selectors.quickstart.enableQuickstartCheckbox + ); + this._enableQuickstartCheckbox.setAttribute( + "label", + TorStrings.settings.quickstartCheckbox + ); + this._enableQuickstartCheckbox.addEventListener("command", e => { + const checked = this._enableQuickstartCheckbox.checked; + TorSettings.quickstart.enabled = checked; + TorSettings.saveToPrefs().applySettings(); + }); + this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled; + Services.obs.addObserver(this, TorSettingsTopics.SettingChanged); + + // Bridge setup + prefpane.querySelector(selectors.bridges.header).innerText = + TorStrings.settings.bridgesHeading; + prefpane.querySelector(selectors.bridges.description).textContent = + TorStrings.settings.bridgesDescription; + { + let learnMore = prefpane.querySelector(selectors.bridges.learnMore); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL); + } + + // Location + { + const locationGroup = prefpane.querySelector( + selectors.bridges.locationGroup + ); + prefpane.querySelector(selectors.bridges.locationLabel).textContent = + TorStrings.settings.bridgeLocation; + const location = prefpane.querySelector(selectors.bridges.location); + const locationEntries = prefpane.querySelector( + selectors.bridges.locationEntries + ); + const chooseForMe = prefpane.querySelector( + selectors.bridges.chooseForMe + ); + chooseForMe.setAttribute( + "label", + TorStrings.settings.bridgeChooseForMe + ); + chooseForMe.addEventListener("command", e => { + TorConnect.beginAutoBootstrap(location.value); + }); + this._populateLocations = () => { + let value = location.value; + locationEntries.textContent = ""; + + { + const item = document.createXULElement("menuitem"); + item.setAttribute("value", ""); + item.setAttribute( + "label", + TorStrings.settings.bridgeLocationAutomatic + ); + locationEntries.appendChild(item); + } + + const codes = TorConnect.countryCodes; + const items = codes.map(code => { + const item = document.createXULElement("menuitem"); + item.setAttribute("value", code); + item.setAttribute( + "label", + TorConnect.countryNames[code] + ? TorConnect.countryNames[code] + : code + ); + return item; + }); + items.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + locationEntries.append(...items); + location.value = value; + }; + this._showAutoconfiguration = () => { + if ( + !TorConnect.shouldShowTorConnect || + !TorProtocolService.torBootstrapErrorOccurred() + ) { + locationGroup.setAttribute("hidden", "true"); + return; + } + // Populate locations, even though we will show only the automatic + // item for a moment. In my opinion showing the button immediately is + // better then waiting for the Moat query to finish (after a while) + // and showing the controls only after that. + this._populateLocations(); + locationGroup.removeAttribute("hidden"); + if (!TorConnect.countryCodes.length) { + TorConnect.getCountryCodes().then(() => this._populateLocations()); + } + }; + this._showAutoconfiguration(); + } + + // Bridge cards + const bridgeHeader = prefpane.querySelector( + selectors.bridges.currentHeader + ); + bridgeHeader.querySelector( + selectors.bridges.currentHeaderText + ).textContent = TorStrings.settings.bridgeCurrent; + const bridgeSwitch = bridgeHeader.querySelector(selectors.bridges.switch); + bridgeSwitch.addEventListener("change", () => { + TorSettings.bridges.enabled = bridgeSwitch.checked; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + const bridgeTemplate = prefpane.querySelector( + selectors.bridges.cardTemplate + ); + { + const learnMore = bridgeTemplate.querySelector( + selectors.bridges.cardLearnMore + ); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute("href", "about:blank"); + } + bridgeTemplate.querySelector( + selectors.bridges.cardConnectedLabel + ).textContent = TorStrings.settings.statusTorConnected; + bridgeTemplate + .querySelector(selectors.bridges.cardCopy) + .setAttribute("label", TorStrings.settings.bridgeCopy); + bridgeTemplate.querySelector(selectors.bridges.cardShare).textContent = + TorStrings.settings.bridgeShare; + const bridgeCards = prefpane.querySelector(selectors.bridges.cards); + const bridgeMenu = prefpane.querySelector(selectors.bridges.cardMenu); + + this._addBridgeCard = bridgeString => { + const card = bridgeTemplate.cloneNode(true); + card.removeAttribute("id"); + const grid = card.querySelector(selectors.bridges.cardQrGrid); + card.addEventListener("click", e => { + let target = e.target; + let apply = true; + while (target !== null && target !== card && apply) { + // Deal with mixture of "command" and "click" events + apply = !target.classList?.contains("stop-click"); + target = target.parentElement; + } + if (apply) { + if (card.classList.toggle("expanded")) { + grid.classList.add("to-animate"); + grid.style.height = `${grid.scrollHeight}px`; + } else { + // Be sure we still have the to-animate class + grid.classList.add("to-animate"); + grid.style.height = ""; + } + } + }); + const emojis = makeBridgeId(bridgeString).map(e => { + const span = document.createElement("span"); + span.className = "emoji"; + span.textContent = e; + return span; + }); + const idString = TorStrings.settings.bridgeId; + const id = card.querySelector(selectors.bridges.cardId); + const details = parseBridgeLine(bridgeString); + if (details && details.id !== undefined) { + card.setAttribute("data-bridge-id", details.id); + } + // TODO: properly handle "vanilla" bridges? + const type = + details && details.transport !== undefined + ? details.transport + : "vanilla"; + for (const piece of idString.split(/(#[12])/)) { + if (piece == "#1") { + id.append(type); + } else if (piece == "#2") { + id.append(...emojis); + } else { + id.append(piece); + } + } + card.querySelector( + selectors.bridges.cardHeadingAddr + ).textContent = bridgeString; + const optionsButton = card.querySelector(selectors.bridges.cardOptions); + if (TorSettings.bridges.source === TorBridgeSource.BuiltIn) { + optionsButton.setAttribute("hidden", "true"); + } else { + // Cloning the menupopup element does not work as expected. + // Therefore, we use only one, and just before opening it, we remove + // its previous items, and add the ones relative to the bridge whose + // button has been pressed. + optionsButton.addEventListener("click", () => { + const menuItem = document.createXULElement("menuitem"); + menuItem.setAttribute("label", TorStrings.settings.remove); + menuItem.classList.add("menuitem-iconic"); + menuItem.image = "chrome://global/skin/icons/delete.svg"; + menuItem.addEventListener("command", e => { + const strings = TorSettings.bridges.bridge_strings; + const index = strings.indexOf(bridgeString); + if (index !== -1) { + strings.splice(index, 1); + } + TorSettings.bridges.enabled = + bridgeSwitch.checked && !!strings.length; + TorSettings.bridges.bridge_strings = strings.join("\n"); + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + if (bridgeMenu.firstChild) { + bridgeMenu.firstChild.remove(); + } + bridgeMenu.append(menuItem); + bridgeMenu.openPopup(optionsButton, { + position: "bottomleft topleft", + }); + }); + } + const bridgeAddr = card.querySelector(selectors.bridges.cardAddr); + bridgeAddr.setAttribute("value", bridgeString); + const bridgeCopy = card.querySelector(selectors.bridges.cardCopy); + let restoreTimeout = null; + bridgeCopy.addEventListener("command", e => { + this.onCopyBridgeAddress(bridgeAddr); + const label = bridgeCopy.querySelector("label"); + label.setAttribute("value", TorStrings.settings.copied); + bridgeCopy.classList.add("primary"); + + const RESTORE_TIME = 1200; + if (restoreTimeout !== null) { + clearTimeout(restoreTimeout); + } + restoreTimeout = setTimeout(() => { + label.setAttribute("value", TorStrings.settings.bridgeCopy); + bridgeCopy.classList.remove("primary"); + restoreTimeout = null; + }, RESTORE_TIME); + }); + if (details && details.id === this._currentBridge) { + card.classList.add("currently-connected"); + bridgeCards.prepend(card); + } else { + bridgeCards.append(card); + } + // Add the QR only after appending the card, to have the computed style + try { + const container = card.querySelector(selectors.bridges.cardQr); + const style = getComputedStyle(container); + const width = style.width.substr(0, style.width.length - 2); + const height = style.height.substr(0, style.height.length - 2); + new QRCode(container, { + text: bridgeString, + width, + height, + colorDark: style.color, + colorLight: style.backgroundColor, + document, + }); + container.parentElement.addEventListener("click", () => { + this.onShowQr(bridgeString); + }); + } catch (err) { + // TODO: Add a generic image in case of errors such as code overflow. + // It should never happen with correct codes, but after all this + // content can be generated by users... + console.error("Could not generate the QR code for the bridge:", err); + } + }; + this._checkBridgeCardsHeight = () => { + for (const card of bridgeCards.children) { + // Expanded cards have the height set manually to their details for + // the CSS animation. However, when resizing the window, we may need + // to adjust their height. + if (card.classList.contains("expanded")) { + const grid = card.querySelector(selectors.bridges.cardQrGrid); + // Reset it first, to avoid having a height that is higher than + // strictly needed. Also, remove the to-animate class, because the + // animation interferes with this process! + grid.classList.remove("to-animate"); + grid.style.height = ""; + grid.style.height = `${grid.scrollHeight}px`; + } + } + }; + this._currentBridgesExpanded = false; + const showAll = prefpane.querySelector(selectors.bridges.showAll); + showAll.setAttribute("label", TorStrings.settings.bridgeShowAll); + showAll.addEventListener("command", () => { + this._currentBridgesExpanded = true; + this._populateBridgeCards(); + }); + const removeAll = prefpane.querySelector(selectors.bridges.removeAll); + removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll); + removeAll.addEventListener("command", () => { + this.onRemoveAllBridges(); + }); + this._populateBridgeCards = async () => { + const collapseThreshold = 4; + + let newStrings = new Set(TorSettings.bridges.bridge_strings); + const numBridges = newStrings.size; + if (!newStrings.size) { + bridgeHeader.setAttribute("hidden", "true"); + bridgeCards.setAttribute("hidden", "true"); + showAll.setAttribute("hidden", "true"); + removeAll.setAttribute("hidden", "true"); + bridgeCards.textContent = ""; + return; + } + bridgeHeader.removeAttribute("hidden"); + bridgeCards.removeAttribute("hidden"); + bridgeSwitch.checked = TorSettings.bridges.enabled; + bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled); + + let shownCards = 0; + const toShow = this._currentBridgesExpanded + ? numBridges + : collapseThreshold; + + // Do not remove all the old cards, because it makes scrollbar "jump" + const currentCards = bridgeCards.querySelectorAll( + selectors.bridges.card + ); + for (const card of currentCards) { + const string = card.querySelector(selectors.bridges.cardAddr).value; + const hadString = newStrings.delete(string); + if (!hadString || shownCards == toShow) { + card.remove(); + } else { + shownCards++; + } + } + + // Add only the new strings that remained in the set + for (const bridge of newStrings) { + if (shownCards >= toShow) { + if (this._currentBridge === "") { + break; + } else if (!bridge.includes(this._currentBridge)) { + continue; + } + } + this._addBridgeCard(bridge); + shownCards++; + } + + // If we know the connected bridge, we may have added more than the ones + // we should actually show (but the connected ones have been prepended, + // if needed). So, remove any exceeding ones. + while (shownCards > toShow) { + bridgeCards.lastElementChild.remove(); + shownCards--; + } + + // And finally update the buttons + if (numBridges > collapseThreshold && !this._currentBridgesExpanded) { + showAll.removeAttribute("hidden"); + if (TorSettings.bridges.enabled) { + showAll.classList.add("primary"); + } else { + showAll.classList.remove("primary"); + } + removeAll.setAttribute("hidden", "true"); + if (TorSettings.bridges.enabled) { + // We do not want both collapsed and disabled at the same time, + // because we use collapsed only to display a gradient on the list. + bridgeCards.classList.add("list-collapsed"); + } + } else { + showAll.setAttribute("hidden", "true"); + removeAll.removeAttribute("hidden"); + bridgeCards.classList.remove("list-collapsed"); + } + }; + this._populateBridgeCards(); + this._updateConnectedBridges = () => { + for (const card of bridgeCards.querySelectorAll( + ".currently-connected" + )) { + card.classList.remove("currently-connected"); + } + if (this._currentBridge === "") { + return; + } + // Make sure we have the connected bridge in the list + this._populateBridgeCards(); + // At the moment, IDs do not have to be unique (and it is a concrete + // case also with built-in bridges!). E.g., one line for the IPv4 + // address and one for the IPv6 address, so use querySelectorAll + const cards = bridgeCards.querySelectorAll( + `[data-bridge-id="${this._currentBridge}"]` + ); + for (const card of cards) { + card.classList.add("currently-connected"); + } + const placeholder = document.createElement("span"); + bridgeCards.prepend(placeholder); + placeholder.replaceWith(...cards); + }; + try { + const { controller } = ChromeUtils.import( + "resource://torbutton/modules/tor-control-port.js", + {} + ); + // Avoid the cache because we set our custom event watcher, and at the + // moment, watchers cannot be removed from a controller. + controller(true).then(aController => { + this._controller = aController; + // Getting the circuits may be enough, if we have bootstrapped for a + // while, but at the beginning it gives many bridges as connected, + // because tor pokes all the bridges to find the best one. + // Also, watching circuit events does not work, at the moment, but in + // any case, checking the stream has the advantage that we can see if + // it really used for a connection, rather than tor having created + // this circuit to check if the bridge can be used. We do this by + // checking if the stream has SOCKS username, which actually contains + // the destination of the stream. + this._controller.watchEvent( + "STREAM", + event => + event.StreamStatus === "SUCCEEDED" && "SOCKS_USERNAME" in event, + async event => { + const circuitStatuses = await this._controller.getInfo( + "circuit-status" + ); + if (!circuitStatuses) { + return; + } + for (const status of circuitStatuses) { + if (status.id === event.CircuitID && status.circuit.length) { + // The id in the circuit begins with a $ sign + const bridgeId = status.circuit[0][0].substr(1); + if (bridgeId !== this._currentBridge) { + this._currentBridge = bridgeId; + this._updateConnectedBridges(); + } + break; + } + } + } + ); + }); + } catch (err) { + console.warn( + "We could not load torbutton, bridge statuses will not be updated", + err + ); + } + + // Add a new bridge + prefpane.querySelector(selectors.bridges.addHeader).textContent = + TorStrings.settings.bridgeAdd; + prefpane + .querySelector(selectors.bridges.addBuiltinLabel) + .setAttribute("value", TorStrings.settings.bridgeSelectBrowserBuiltin); + { + let button = prefpane.querySelector(selectors.bridges.addBuiltinButton); + button.setAttribute("label", TorStrings.settings.bridgeSelectBuiltin); + button.addEventListener("command", e => { + this.onAddBuiltinBridge(); + }); + } + prefpane + .querySelector(selectors.bridges.requestLabel) + .setAttribute("value", TorStrings.settings.bridgeRequestFromTorProject); + { + let button = prefpane.querySelector(selectors.bridges.requestButton); + button.setAttribute("label", TorStrings.settings.bridgeRequest); + button.addEventListener("command", e => { + this.onRequestBridge(); + }); + } + prefpane + .querySelector(selectors.bridges.enterLabel) + .setAttribute("value", TorStrings.settings.bridgeEnterKnown); + { + const button = prefpane.querySelector(selectors.bridges.enterButton); + button.setAttribute("label", TorStrings.settings.bridgeAddManually); + button.addEventListener("command", e => { + this.onAddBridgeManually(); + }); + } + + Services.obs.addObserver(this, TorConnectTopics.StateChange); + + // Advanced setup + prefpane.querySelector(selectors.advanced.header).innerText = + TorStrings.settings.advancedHeading; + prefpane.querySelector(selectors.advanced.label).textContent = + TorStrings.settings.advancedLabel; + { + let settingsButton = prefpane.querySelector(selectors.advanced.button); + settingsButton.setAttribute( + "label", + TorStrings.settings.advancedButton + ); + settingsButton.addEventListener("command", () => { + this.onAdvancedSettings(); + }); + } + + // Tor logs + prefpane + .querySelector(selectors.advanced.torLogsLabel) + .setAttribute("value", TorStrings.settings.showTorDaemonLogs); + let torLogsButton = prefpane.querySelector( + selectors.advanced.torLogsButton + ); + torLogsButton.setAttribute("label", TorStrings.settings.showLogs); + torLogsButton.addEventListener("command", () => { + this.onViewTorLogs(); + }); + }, + + init() { + this._populateXUL(); + + let onUnload = () => { + window.removeEventListener("unload", onUnload); + gConnectionPane.uninit(); + }; + window.addEventListener("unload", onUnload); + + window.addEventListener("resize", () => { + this._checkBridgeCardsHeight(); + }); + }, + + uninit() { + // unregister our observer topics + Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged); + Services.obs.removeObserver(this, TorConnectTopics.StateChange); + + if (this._controller !== null) { + this._controller.close(); + this._controller = null; + } + }, + + // whether the page should be present in about:preferences + get enabled() { + return TorProtocolService.ownsTorDaemon; + }, + + // + // Callbacks + // + + observe(subject, topic, data) { + switch (topic) { + // triggered when a TorSettings param has changed + case TorSettingsTopics.SettingChanged: { + let obj = subject?.wrappedJSObject; + switch (data) { + case TorSettingsData.QuickStartEnabled: { + this._enableQuickstartCheckbox.checked = obj.value; + break; + } + } + break; + } + // triggered when tor connect state changes and we may + // need to update the messagebox + case TorConnectTopics.StateChange: { + this.onStateChange(); + break; + } + } + }, + + async onInternetTest() { + const mrpc = new MoatRPC(); + let status = null; + try { + await mrpc.init(); + status = await mrpc.testInternetConnection(); + } catch (err) { + console.log("Error while checking the Internet connection", err); + } finally { + mrpc.uninit(); + } + if (status) { + this._internetStatus = status.successful + ? InternetStatus.Online + : InternetStatus.Offline; + this._populateStatus(); + } + }, + + onStateChange() { + this._populateMessagebox(); + this._populateStatus(); + this._showAutoconfiguration(); + this._populateBridgeCards(); + }, + + onShowQr(bridgeString) { + const dialog = new BridgeQrDialog(); + dialog.openDialog(gSubDialog, bridgeString); + }, + + onCopyBridgeAddress(addressElem) { + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( + Ci.nsIClipboardHelper + ); + clipboard.copyString(addressElem.value); + }, + + onRemoveAllBridges() { + TorSettings.bridges.enabled = false; + TorSettings.bridges.bridge_strings = ""; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }, + + onAddBuiltinBridge() { + let builtinBridgeDialog = new BuiltinBridgeDialog(); + + let sizeObserver = null; + { + let ds = document.querySelector("#dialogStack"); + let boxObserver; + boxObserver = new MutationObserver(() => { + let dialogBox = document.querySelector(".dialogBox"); + if (dialogBox) { + sizeObserver = new MutationObserver(mutations => { + for (const m of mutations) { + if (m.attributeName === "style") { + builtinBridgeDialog.resized(); + break; + } + } + }); + sizeObserver.observe(dialogBox, { attributes: true }); + boxObserver.disconnect(); + } + }); + boxObserver.observe(ds, { childList: true, subtree: true }); + } + + builtinBridgeDialog.openDialog(gSubDialog, aBridgeType => { + sizeObserver.disconnect(); + + if (!aBridgeType) { + TorSettings.bridges.enabled = false; + TorSettings.bridges.builtin_type = ""; + } else { + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BuiltIn; + TorSettings.bridges.builtin_type = aBridgeType; + } + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + }, + + // called when the request bridge button is activated + onRequestBridge() { + let requestBridgeDialog = new RequestBridgeDialog(); + requestBridgeDialog.openDialog(gSubDialog, aBridges => { + if (aBridges.length) { + let bridgeStrings = aBridges.join("\n"); + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BridgeDB; + TorSettings.bridges.bridge_strings = bridgeStrings; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + } else { + TorSettings.bridges.enabled = false; + } + }); + }, + + onAddBridgeManually() { + let provideBridgeDialog = new ProvideBridgeDialog(); + provideBridgeDialog.openDialog(gSubDialog, aBridgeString => { + if (aBridgeString.length) { + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.UserProvided; + TorSettings.bridges.bridge_strings = aBridgeString; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + } else { + TorSettings.bridges.enabled = false; + TorSettings.bridges.source = TorBridgeSource.Invalid; + } + }); + }, + + onAdvancedSettings() { + let connectionSettingsDialog = new ConnectionSettingsDialog(); + connectionSettingsDialog.openDialog(gSubDialog); + }, + + onViewTorLogs() { + let torLogDialog = new TorLogDialog(); + torLogDialog.openDialog(gSubDialog); + }, + }; + return retval; +})(); /* gConnectionPane */ + +function makeBridgeId(bridgeString) { + // JS uses UTF-16. While most of these emojis are surrogate pairs, a few + // ones fit one UTF-16 character. So we could not use neither indices, + // nor substr, nor some function to split the string. + const emojis = [ + "😄️", + "😒️", + "😉", + "😭️", + "😂️", + "😎️", + "🤩️", + "😘", + "😜️", + "😏️", + "😷", + "🤢", + "🤕", + "🤧", + "🥵", + "🥶", + "🥴", + "😵️", + "🤮️", + "🤑", + "🤔", + "🫢", + "🤐", + "😮💨", + "😐", + "🤤", + "😴", + "🤯", + "🤠", + "🥳", + "🥸", + "🤓", + "🧐", + "😨", + "😳", + "🥺", + "🤬", + "😈", + "👿", + "💀", + "💩", + "🤡", + "👺", + "👻", + "👽", + "🦴", + "🤖", + "😸", + "🙈", + "🙉", + "🙊", + "💋", + "💖", + "💯", + "💢", + "💧", + "💨", + "💭", + "💤", + "👋", + "👌", + "✌", + "👍", + "👎", + "🤛", + "🙌", + "💪", + "🙏", + "✍", + "🧠", + "👀", + "👂", + "👅", + "🦷", + "🐾", + "🐶", + "🦊", + "🦝", + "🐈", + "🦁", + "🐯", + "🐴", + "🦄", + "🦓", + "🐮", + "🐷", + "🐑", + "🐪", + "🐘", + "🐭", + "🐰", + "🦔", + "🦇", + "🐻", + "🐨", + "🐼", + "🐔", + "🦨", + "🦘", + "🐦", + "🐧", + "🦩", + "🦉", + "🦜", + "🪶", + "🐸", + "🐊", + "🐢", + "🦎", + "🐍", + "🦖", + "🦀", + "🐬", + "🐙", + "🐌", + "🐝", + "🐞", + "🌸", + "🌲", + "🌵", + "🍀", + "🍁", + "🍇", + "🍉", + "🍊", + "🍋", + "🍌", + "🍍", + "🍎", + "🥥", + "🍐", + "🍒", + "🍓", + "🫐", + "🥝", + "🥔", + "🥕", + "🧅", + "🌰", + "🍄", + "🍞", + "🥞", + "🧀", + "🍖", + "🍔", + "🍟", + "🍕", + "🥚", + "🍿", + "🧂", + "🍙", + "🍦", + "🍩", + "🍪", + "🎂", + "🍬", + "🍭", + "🥛", + "☕", + "🫖", + "🍾", + "🍷", + "🍹", + "🍺", + "🍴", + "🥄", + "🫙", + "🧭", + "🌋", + "🪵", + "🏡", + "🏢", + "🏰", + "⛲", + "⛺", + "🎡", + "🚂", + "🚘", + "🚜", + "🚲", + "🚔", + "🚨", + "⛽", + "🚥", + "🚧", + "⚓", + "⛵", + "🛟", + "🪂", + "🚀", + "⌛", + "⏰", + "🌂", + "🌞", + "🌙", + "🌟", + "⛅", + "⚡", + "🔥", + "🌊", + "🎃", + "🎈", + "🎉", + "✨", + "🎀", + "🎁", + "🏆", + "🏅", + "🔮", + "🪄", + "🎾", + "🎳", + "🎲", + "🎭", + "🎨", + "🧵", + "🎩", + "📢", + "🔔", + "🎵", + "🎤", + "🎧", + "🎷", + "🎸", + "🥁", + "🔋", + "🔌", + "💻", + "💾", + "💿", + "🎬", + "📺", + "📷", + "🎮", + "🧩", + "🔍", + "💡", + "📖", + "💰", + "💼", + "📈", + "📌", + "📎", + "🔒", + "🔑", + "🔧", + "🪛", + "🔩", + "🧲", + "🔬", + "🔭", + "📡", + "🚪", + "🪑", + "⛔", + "🚩", + ]; + + // FNV-1a implementation that is compatible with other languages + const prime = 0x01000193; + const offset = 0x811c9dc5; + let hash = offset; + const encoder = new TextEncoder(); + for (const charCode of encoder.encode(bridgeString)) { + hash = Math.imul(hash ^ charCode, prime); + } + + const hashBytes = [ + ((hash & 0x7f000000) >> 24) | (hash < 0 ? 0x80 : 0), + (hash & 0x00ff0000) >> 16, + (hash & 0x0000ff00) >> 8, + hash & 0x000000ff, + ]; + return hashBytes.map(b => emojis[b]); +} + +function parseBridgeLine(line) { + const re = /^([^\s]+\s+)?([0-9a-fA-F.[]:]+:[0-9]{1,5})\s*([0-9a-fA-F]{40})(\s+.+)?/; + const matches = line.match(re); + if (!matches) { + return null; + } + let bridge = { addr: matches[2] }; + if (matches[1] !== undefined) { + bridge.transport = matches[1].trim(); + } + if (matches[3] !== undefined) { + bridge.id = matches[3].toUpperCase(); + } + if (matches[4] !== undefined) { + bridge.args = matches[4].trim(); + } + return bridge; +} diff --git a/browser/components/torpreferences/content/connectionPane.xhtml b/browser/components/torpreferences/content/connectionPane.xhtml new file mode 100644 index 0000000000000..67f98685d8038 --- /dev/null +++ b/browser/components/torpreferences/content/connectionPane.xhtml @@ -0,0 +1,177 @@ +<!-- Tor panel --> + +<script type="application/javascript" + src="chrome://browser/content/torpreferences/connectionPane.js"/> +<html:template id="template-paneConnection"> + +<!-- Tor Connect Message Box --> +<groupbox data-category="paneConnection" hidden="true"> + <html:div id="torPreferences-connectMessageBox" + class="subcategory" + data-category="paneConnection" + hidden="true"> + html:table + html:tr + html:td + <html:div id="torPreferences-connectMessageBox-icon"/> + </html:td> + <html:td id="torPreferences-connectMessageBox-message"> + </html:td> + html:td + <html:button id="torPreferences-connectMessageBox-button"> + </html:button> + </html:td> + </html:tr> + </html:table> + </html:div> +</groupbox> + +<hbox id="torPreferencesCategory" + class="subcategory" + data-category="paneConnection" + hidden="true"> + <html:h1 id="torPreferences-header"/> +</hbox> + +<groupbox data-category="paneConnection" + hidden="true"> + <description flex="1"> + <html:span id="torPreferences-description" class="tail-with-learn-more"/> + <label id="torPreferences-learnMore" class="learnMore text-link" is="text-link"/> + </description> +</groupbox> + +<groupbox id="torPreferences-status-group" + data-category="paneConnection"> + <hbox id="torPreferences-status-box"> + <image id="torPreferences-status-internet-icon"/> + <html:span id="torPreferences-status-internet-label"/> + <button id="torPreferences-status-internet-test"/> + <image id="torPreferences-status-internet-statusIcon"/> + <html:span id="torPreferences-status-internet-status"/> + <image id="torPreferences-status-tor-icon"/> + <html:span id="torPreferences-status-tor-label"/> + <image id="torPreferences-status-tor-statusIcon"/> + <html:span id="torPreferences-status-tor-status"/> + </hbox> +</groupbox> + +<!-- Quickstart --> +<groupbox id="torPreferences-quickstart-group" + data-category="paneConnection" + hidden="true"> + <html:h2 id="torPreferences-quickstart-header"/> + <description flex="1"> + <html:span id="torPreferences-quickstart-description"/> + </description> + <checkbox id="torPreferences-quickstart-toggle"/> +</groupbox> + +<!-- Bridges --> +<hbox class="subcategory" + data-category="paneConnection" + hidden="true"> + <html:h1 id="torPreferences-bridges-header"/> +</hbox> +<groupbox id="torPreferences-bridges-group" + data-category="paneConnection" + hidden="true"> + <description flex="1"> + <html:span id="torPreferences-bridges-description" class="tail-with-learn-more"/> + <label id="torPreferences-bridges-learnMore" class="learnMore text-link" is="text-link"/> + </description> + <hbox align="center" id="torPreferences-bridges-locationGroup" hidden="true"> + <label id="torPreferences-bridges-locationLabel" + control="torPreferences-bridges-location"/> + <spacer flex="1"/> + <menulist id="torPreferences-bridges-location"> + <menupopup id="torPreferences-bridges-locationEntries"/> + </menulist> + <button id="torPreferences-bridges-buttonChooseBridgeForMe" class="torMarginFix primary"/> + </hbox> + <html:h2 id="torPreferences-currentBridges-header"> + <html:span id="torPreferences-currentBridges-headerText"/> + <html:input type="checkbox" id="torPreferences-currentBridges-switch" class="toggle-button"/> + </html:h2> + <menupopup id="torPreferences-bridgeCard-menu"/> + <vbox id="torPreferences-bridgeCard-template" class="torPreferences-bridgeCard"> + <hbox class="torPreferences-bridgeCard-heading"> + <html:div class="torPreferences-bridgeCard-id"/> + <html:div class="torPreferences-bridgeCard-headingAddr"/> + <html:div class="torPreferences-bridgeCard-buttons"> + <html:span class="torPreferences-bridgeCard-connectedBadge"> + <image class="torPreferences-bridgeCard-connectedIcon"/> + <html:span class="torPreferences-bridgeCard-connectedLabel"/> + </html:span> + <html:button class="torPreferences-bridgeCard-options stop-click"/> + </html:div> + </hbox> + <box class="torPreferences-bridgeCard-grid"> + <box class="torPreferences-bridgeCard-qrWrapper"> + <box class="torPreferences-bridgeCard-qr stop-click"> + <html:div class="torPreferences-bridgeCard-qrCode"/> + <html:div class="torPreferences-bridgeCard-qrOnionBox"/> + <html:div class="torPreferences-bridgeCard-qrOnion"/> + </box> + <html:div class="torPreferences-bridgeCard-filler"/> + </box> + <description class="torPreferences-bridgeCard-share"></description> + <hbox class="torPreferences-bridgeCard-addrBox"> + <html:input class="torPreferences-bridgeCard-addr torMarginFix stop-click" type="text" readonly="readonly"/> + </hbox> + <hbox class="torPreferences-bridgeCard-learnMoreBox" align="center"> + <label class="torPreferences-bridgeCard-learnMore learnMore text-link stop-click" is="text-link"/> + </hbox> + <hbox class="torPreferences-bridgeCard-copy" align="center"> + <button class="torPreferences-bridgeCard-copyButton stop-click"/> + </hbox> + </box> + </vbox> + <vbox id="torPreferences-currentBridges-cards"></vbox> + <vbox align="center"> + <button id="torPreferences-currentBridges-showAll"/> + <button id="torPreferences-currentBridges-removeAll" class="primary danger-button"/> + </vbox> + <html:h2 id="torPreferences-addBridge-header"></html:h2> + <hbox align="center"> + <label id="torPreferences-addBridge-labelBuiltinBridge"/> + <space flex="1"/> + <button id="torPreferences-addBridge-buttonBuiltinBridge" class="torMarginFix"/> + </hbox> + <hbox align="center"> + <label id="torPreferences-addBridge-labelRequestBridge"/> + <space flex="1"/> + <button id="torPreferences-addBridge-buttonRequestBridge" class="torMarginFix"/> + </hbox> + <hbox align="center"> + <label id="torPreferences-addBridge-labelEnterBridge"/> + <space flex="1"/> + <button id="torPreferences-addBridge-buttonEnterBridge" class="torMarginFix"/> + </hbox> +</groupbox> + +<!-- Advanced --> +<hbox class="subcategory" + data-category="paneConnection" + hidden="true"> + <html:h1 id="torPreferences-advanced-header"/> +</hbox> +<groupbox id="torPreferences-advanced-group" + data-category="paneConnection" + hidden="true"> + <box id="torPreferences-advanced-grid"> + <hbox id="torPreferences-advanced-hbox" align="center"> + <label id="torPreferences-advanced-label"/> + </hbox> + <hbox align="center"> + <button id="torPreferences-advanced-button"/> + </hbox> + <hbox id="torPreferences-torDaemon-hbox" align="center"> + <label id="torPreferences-torLogs"/> + </hbox> + <hbox align="center" data-subcategory="viewlogs"> + <button id="torPreferences-buttonTorLogs"/> + </hbox> + </box> +</groupbox> +</html:template> diff --git a/browser/components/torpreferences/content/connectionSettingsDialog.jsm b/browser/components/torpreferences/content/connectionSettingsDialog.jsm new file mode 100644 index 0000000000000..abc177c43f884 --- /dev/null +++ b/browser/components/torpreferences/content/connectionSettingsDialog.jsm @@ -0,0 +1,393 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["ConnectionSettingsDialog"]; + +const { TorSettings, TorProxyType } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +class ConnectionSettingsDialog { + constructor() { + this._dialog = null; + this._useProxyCheckbox = null; + this._proxyTypeLabel = null; + this._proxyTypeMenulist = null; + this._proxyAddressLabel = null; + this._proxyAddressTextbox = null; + this._proxyPortLabel = null; + this._proxyPortTextbox = null; + this._proxyUsernameLabel = null; + this._proxyUsernameTextbox = null; + this._proxyPasswordLabel = null; + this._proxyPasswordTextbox = null; + this._useFirewallCheckbox = null; + this._allowedPortsLabel = null; + this._allowedPortsTextbox = null; + } + + static get selectors() { + return { + header: "#torPreferences-connection-header", + useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy", + proxyTypeLabel: "label#torPreferences-localProxy-type", + proxyTypeList: "menulist#torPreferences-localProxy-builtinList", + proxyAddressLabel: "label#torPreferences-localProxy-address", + proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress", + proxyPortLabel: "label#torPreferences-localProxy-port", + proxyPortTextbox: "input#torPreferences-localProxy-textboxPort", + proxyUsernameLabel: "label#torPreferences-localProxy-username", + proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername", + proxyPasswordLabel: "label#torPreferences-localProxy-password", + proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword", + useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall", + firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts", + firewallAllowedPortsTextbox: + "input#torPreferences-connection-textboxAllowedPorts", + }; + } + + // disables the provided list of elements + _setElementsDisabled(elements, disabled) { + for (let currentElement of elements) { + currentElement.disabled = disabled; + } + } + + _populateXUL(window, aDialog) { + const selectors = ConnectionSettingsDialog.selectors; + + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + dialogWin.setAttribute( + "title", + TorStrings.settings.connectionSettingsDialogTitle + ); + this._dialog.querySelector(selectors.header).textContent = + TorStrings.settings.connectionSettingsDialogHeader; + + // Local Proxy + this._useProxyCheckbox = this._dialog.querySelector( + selectors.useProxyCheckbox + ); + this._useProxyCheckbox.setAttribute( + "label", + TorStrings.settings.useLocalProxy + ); + this._useProxyCheckbox.addEventListener("command", e => { + const checked = this._useProxyCheckbox.checked; + this.onToggleProxy(checked); + }); + this._proxyTypeLabel = this._dialog.querySelector(selectors.proxyTypeLabel); + this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType); + + let mockProxies = [ + { + value: TorProxyType.Socks4, + label: TorStrings.settings.proxyTypeSOCKS4, + }, + { + value: TorProxyType.Socks5, + label: TorStrings.settings.proxyTypeSOCKS5, + }, + { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP }, + ]; + this._proxyTypeMenulist = this._dialog.querySelector( + selectors.proxyTypeList + ); + this._proxyTypeMenulist.addEventListener("command", e => { + const value = this._proxyTypeMenulist.value; + this.onSelectProxyType(value); + }); + for (let currentProxy of mockProxies) { + let menuEntry = window.document.createXULElement("menuitem"); + menuEntry.setAttribute("value", currentProxy.value); + menuEntry.setAttribute("label", currentProxy.label); + this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry); + } + + this._proxyAddressLabel = this._dialog.querySelector( + selectors.proxyAddressLabel + ); + this._proxyAddressLabel.setAttribute( + "value", + TorStrings.settings.proxyAddress + ); + this._proxyAddressTextbox = this._dialog.querySelector( + selectors.proxyAddressTextbox + ); + this._proxyAddressTextbox.setAttribute( + "placeholder", + TorStrings.settings.proxyAddressPlaceholder + ); + this._proxyAddressTextbox.addEventListener("blur", e => { + let value = this._proxyAddressTextbox.value.trim(); + let colon = value.lastIndexOf(":"); + if (colon != -1) { + let maybePort = parseInt(value.substr(colon + 1)); + if (!isNaN(maybePort) && maybePort > 0 && maybePort < 65536) { + this._proxyAddressTextbox.value = value.substr(0, colon); + this._proxyPortTextbox.value = maybePort; + } + } + }); + this._proxyPortLabel = this._dialog.querySelector(selectors.proxyPortLabel); + this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort); + this._proxyPortTextbox = this._dialog.querySelector( + selectors.proxyPortTextbox + ); + this._proxyUsernameLabel = this._dialog.querySelector( + selectors.proxyUsernameLabel + ); + this._proxyUsernameLabel.setAttribute( + "value", + TorStrings.settings.proxyUsername + ); + this._proxyUsernameTextbox = this._dialog.querySelector( + selectors.proxyUsernameTextbox + ); + this._proxyUsernameTextbox.setAttribute( + "placeholder", + TorStrings.settings.proxyUsernamePasswordPlaceholder + ); + this._proxyPasswordLabel = this._dialog.querySelector( + selectors.proxyPasswordLabel + ); + this._proxyPasswordLabel.setAttribute( + "value", + TorStrings.settings.proxyPassword + ); + this._proxyPasswordTextbox = this._dialog.querySelector( + selectors.proxyPasswordTextbox + ); + this._proxyPasswordTextbox.setAttribute( + "placeholder", + TorStrings.settings.proxyUsernamePasswordPlaceholder + ); + + this.onToggleProxy(false); + if (TorSettings.proxy.enabled) { + this.onToggleProxy(true); + this.onSelectProxyType(TorSettings.proxy.type); + this._proxyAddressTextbox.value = TorSettings.proxy.address; + this._proxyPortTextbox.value = TorSettings.proxy.port; + this._proxyUsernameTextbox.value = TorSettings.proxy.username; + this._proxyPasswordTextbox.value = TorSettings.proxy.password; + } + + // Local firewall + this._useFirewallCheckbox = this._dialog.querySelector( + selectors.useFirewallCheckbox + ); + this._useFirewallCheckbox.setAttribute( + "label", + TorStrings.settings.useFirewall + ); + this._useFirewallCheckbox.addEventListener("command", e => { + const checked = this._useFirewallCheckbox.checked; + this.onToggleFirewall(checked); + }); + this._allowedPortsLabel = this._dialog.querySelector( + selectors.firewallAllowedPortsLabel + ); + this._allowedPortsLabel.setAttribute( + "value", + TorStrings.settings.allowedPorts + ); + this._allowedPortsTextbox = this._dialog.querySelector( + selectors.firewallAllowedPortsTextbox + ); + this._allowedPortsTextbox.setAttribute( + "placeholder", + TorStrings.settings.allowedPortsPlaceholder + ); + + this.onToggleFirewall(false); + if (TorSettings.firewall.enabled) { + this.onToggleFirewall(true); + this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join( + ", " + ); + } + + this._dialog.addEventListener("dialogaccept", e => { + this._applySettings(); + }); + } + + // callback when proxy is toggled + onToggleProxy(enabled) { + this._useProxyCheckbox.checked = enabled; + let disabled = !enabled; + + this._setElementsDisabled( + [ + this._proxyTypeLabel, + this._proxyTypeMenulist, + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + disabled + ); + if (enabled) { + this.onSelectProxyType(this._proxyTypeMenulist.value); + } + } + + // callback when proxy type is changed + onSelectProxyType(value) { + if (typeof value === "string") { + value = parseInt(value); + } + + this._proxyTypeMenulist.value = value; + switch (value) { + case TorProxyType.Invalid: { + this._setElementsDisabled( + [ + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + true + ); // DISABLE + + this._proxyAddressTextbox.value = ""; + this._proxyPortTextbox.value = ""; + this._proxyUsernameTextbox.value = ""; + this._proxyPasswordTextbox.value = ""; + break; + } + case TorProxyType.Socks4: { + this._setElementsDisabled( + [ + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + ], + false + ); // ENABLE + this._setElementsDisabled( + [ + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + true + ); // DISABLE + + this._proxyUsernameTextbox.value = ""; + this._proxyPasswordTextbox.value = ""; + break; + } + case TorProxyType.Socks5: + case TorProxyType.HTTPS: { + this._setElementsDisabled( + [ + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + false + ); // ENABLE + break; + } + } + } + + // callback when firewall proxy is toggled + onToggleFirewall(enabled) { + this._useFirewallCheckbox.checked = enabled; + let disabled = !enabled; + + this._setElementsDisabled( + [this._allowedPortsLabel, this._allowedPortsTextbox], + disabled + ); + } + + // pushes settings from UI to tor + _applySettings() { + const type = this._useProxyCheckbox.checked + ? parseInt(this._proxyTypeMenulist.value) + : TorProxyType.Invalid; + const address = this._proxyAddressTextbox.value; + const port = this._proxyPortTextbox.value; + const username = this._proxyUsernameTextbox.value; + const password = this._proxyPasswordTextbox.value; + switch (type) { + case TorProxyType.Invalid: + TorSettings.proxy.enabled = false; + break; + case TorProxyType.Socks4: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + break; + case TorProxyType.Socks5: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + TorSettings.proxy.username = username; + TorSettings.proxy.password = password; + break; + case TorProxyType.HTTPS: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + TorSettings.proxy.username = username; + TorSettings.proxy.password = password; + break; + } + + let portListString = this._useFirewallCheckbox.checked + ? this._allowedPortsTextbox.value + : ""; + if (portListString) { + TorSettings.firewall.enabled = true; + TorSettings.firewall.allowed_ports = portListString; + } else { + TorSettings.firewall.enabled = false; + } + + TorSettings.saveToPrefs(); + TorSettings.applySettings(); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, aDialog); + }, 0); + } + + openDialog(gSubDialog) { + gSubDialog.open( + "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/connectionSettingsDialog.xhtml b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml new file mode 100644 index 0000000000000..88aa979256f02 --- /dev/null +++ b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-connection-dialog" + buttons="accept,cancel"> + <html:h3 id="torPreferences-connection-header">​</html:h3> + <box id="torPreferences-connection-grid"> + <!-- Local Proxy --> + <hbox class="torPreferences-connection-checkbox-container"> + <checkbox id="torPreferences-connection-toggleProxy" label="​"/> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-localProxy-type"/> + </hbox> + <hbox align="center"> + <spacer flex="1"/> + <menulist id="torPreferences-localProxy-builtinList" class="torMarginFix"> + <menupopup/> + </menulist> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-localProxy-address"/> + </hbox> + <hbox align="center"> + <html:input id="torPreferences-localProxy-textboxAddress" type="text" class="torMarginFix"/> + <label id="torPreferences-localProxy-port"/> + <!-- proxy-port-input class style pulled from preferences.css and used in the vanilla proxy setup menu --> + <html:input id="torPreferences-localProxy-textboxPort" class="proxy-port-input torMarginFix" hidespinbuttons="true" type="number" min="0" max="65535" maxlength="5"/> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-localProxy-username"/> + </hbox> + <hbox align="center"> + <html:input id="torPreferences-localProxy-textboxUsername" type="text" class="torMarginFix"/> + <label id="torPreferences-localProxy-password"/> + <html:input id="torPreferences-localProxy-textboxPassword" class="torMarginFix" type="password"/> + </hbox> + <!-- Firewall --> + <hbox class="torPreferences-connection-checkbox-container"> + <checkbox id="torPreferences-connection-toggleFirewall" label="​"/> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-connection-allowedPorts"/> + </hbox> + <hbox align="center"> + <html:input id="torPreferences-connection-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/> + </hbox> + </box> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let connectionSettingsDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-connection-dialog"); + connectionSettingsDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/network.svg b/browser/components/torpreferences/content/network.svg new file mode 100644 index 0000000000000..e1689b5e6d649 --- /dev/null +++ b/browser/components/torpreferences/content/network.svg @@ -0,0 +1,6 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity"> + <path d="M8.5 1a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm2.447 1.75a6.255 6.255 0 0 1 3.756 5.125l-2.229 0A9.426 9.426 0 0 0 10.54 2.75l.407 0zm-2.049 0a8.211 8.211 0 0 1 2.321 5.125l-5.438 0A8.211 8.211 0 0 1 8.102 2.75l.796 0zm-2.846 0 .408 0a9.434 9.434 0 0 0-1.934 5.125l-2.229 0A6.254 6.254 0 0 1 6.052 2.75zm0 11.5a6.252 6.252 0 0 1-3.755-5.125l2.229 0A9.426 9.426 0 0 0 6.46 14.25l-.408 0zm2.05 0a8.211 8.211 0 0 1-2.321-5.125l5.437 0a8.211 8.211 0 0 1-2.321 5.125l-.795 0zm2.846 0-.40 [...] +</svg> diff --git a/browser/components/torpreferences/content/provideBridgeDialog.jsm b/browser/components/torpreferences/content/provideBridgeDialog.jsm new file mode 100644 index 0000000000000..b73a6f533afa6 --- /dev/null +++ b/browser/components/torpreferences/content/provideBridgeDialog.jsm @@ -0,0 +1,69 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["ProvideBridgeDialog"]; + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +const { TorSettings, TorBridgeSource } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +class ProvideBridgeDialog { + constructor() { + this._dialog = null; + this._textarea = null; + this._bridgeString = ""; + } + + static get selectors() { + return { + header: "#torPreferences-provideBridge-header", + textarea: "#torPreferences-provideBridge-textarea", + }; + } + + _populateXUL(aDialog) { + const selectors = ProvideBridgeDialog.selectors; + + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + dialogWin.setAttribute("title", TorStrings.settings.provideBridgeTitle); + this._dialog.querySelector(selectors.header).textContent = + TorStrings.settings.provideBridgeHeader; + this._textarea = this._dialog.querySelector(selectors.textarea); + this._textarea.setAttribute( + "placeholder", + TorStrings.settings.provideBridgePlaceholder + ); + if ( + TorSettings.bridges.enabled && + TorSettings.bridges.source == TorBridgeSource.UserProvided + ) { + this._textarea.value = TorSettings.bridges.bridge_strings.join("\n"); + } + + this._dialog.addEventListener("dialogaccept", e => { + this._bridgeString = this._textarea.value; + }); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(aDialog); + }, 0); + } + + openDialog(gSubDialog, aCloseCallback) { + gSubDialog.open( + "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml", + { + features: "resizable=yes", + closingCallback: () => { + aCloseCallback(this._bridgeString); + }, + }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/provideBridgeDialog.xhtml b/browser/components/torpreferences/content/provideBridgeDialog.xhtml new file mode 100644 index 0000000000000..28d19cadaf9c9 --- /dev/null +++ b/browser/components/torpreferences/content/provideBridgeDialog.xhtml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-provideBridge-dialog" + buttons="help,accept,cancel"> + <html:h3 id="torPreferences-provideBridge-header">​</html:h3> + <html:textarea id="torPreferences-provideBridge-textarea" multiline="true" rows="3"/> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let provideBridgeDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-provideBridge-dialog"); + provideBridgeDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/requestBridgeDialog.jsm b/browser/components/torpreferences/content/requestBridgeDialog.jsm new file mode 100644 index 0000000000000..f14bbdcbbb448 --- /dev/null +++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm @@ -0,0 +1,211 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["RequestBridgeDialog"]; + +const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm"); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +class RequestBridgeDialog { + constructor() { + this._dialog = null; + this._submitButton = null; + this._dialogHeader = null; + this._captchaImage = null; + this._captchaEntryTextbox = null; + this._captchaRefreshButton = null; + this._incorrectCaptchaHbox = null; + this._incorrectCaptchaLabel = null; + this._bridges = []; + } + + static get selectors() { + return { + submitButton: + "accept" /* not really a selector but a key for dialog's getButton */, + dialogHeader: "h3#torPreferences-requestBridge-header", + captchaImage: "image#torPreferences-requestBridge-captchaImage", + captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox", + refreshCaptchaButton: + "button#torPreferences-requestBridge-refreshCaptchaButton", + incorrectCaptchaHbox: + "hbox#torPreferences-requestBridge-incorrectCaptchaHbox", + incorrectCaptchaLabel: + "label#torPreferences-requestBridge-incorrectCaptchaError", + }; + } + + _populateXUL(window, dialog) { + const selectors = RequestBridgeDialog.selectors; + + this._dialog = dialog; + const dialogWin = dialog.parentElement; + dialogWin.setAttribute( + "title", + TorStrings.settings.requestBridgeDialogTitle + ); + // user may have opened a Request Bridge dialog in another tab, so update the + // CAPTCHA image or close out the dialog if we have a bridge list + this._dialog.addEventListener("focusin", () => { + const uri = BridgeDB.currentCaptchaImage; + const bridges = BridgeDB.currentBridges; + + // new captcha image + if (uri) { + this._setcaptchaImage(uri); + } else if (bridges) { + this._bridges = bridges; + this._submitButton.disabled = false; + this._dialog.cancelDialog(); + } + }); + + this._submitButton = this._dialog.getButton(selectors.submitButton); + this._submitButton.setAttribute("label", TorStrings.settings.submitCaptcha); + this._submitButton.disabled = true; + this._dialog.addEventListener("dialogaccept", e => { + e.preventDefault(); + this.onSubmitCaptcha(); + }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + "https://tb-manual.torproject.org/bridges/", + "tab" + ); + }); + + this._dialogHeader = this._dialog.querySelector(selectors.dialogHeader); + this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB; + + this._captchaImage = this._dialog.querySelector(selectors.captchaImage); + + // request captcha from bridge db + BridgeDB.requestNewCaptchaImage().then(uri => { + this._setcaptchaImage(uri); + }); + + this._captchaEntryTextbox = this._dialog.querySelector( + selectors.captchaEntryTextbox + ); + this._captchaEntryTextbox.setAttribute( + "placeholder", + TorStrings.settings.captchaTextboxPlaceholder + ); + this._captchaEntryTextbox.disabled = true; + // disable submit if entry textbox is empty + this._captchaEntryTextbox.oninput = () => { + this._submitButton.disabled = this._captchaEntryTextbox.value == ""; + }; + + this._captchaRefreshButton = this._dialog.querySelector( + selectors.refreshCaptchaButton + ); + this._captchaRefreshButton.disabled = true; + + this._incorrectCaptchaHbox = this._dialog.querySelector( + selectors.incorrectCaptchaHbox + ); + this._incorrectCaptchaLabel = this._dialog.querySelector( + selectors.incorrectCaptchaLabel + ); + this._incorrectCaptchaLabel.setAttribute( + "value", + TorStrings.settings.incorrectCaptcha + ); + + return true; + } + + _setcaptchaImage(uri) { + if (uri != this._captchaImage.src) { + this._captchaImage.src = uri; + this._dialogHeader.textContent = TorStrings.settings.solveTheCaptcha; + this._setUIDisabled(false); + this._captchaEntryTextbox.focus(); + this._captchaEntryTextbox.select(); + } + } + + _setUIDisabled(disabled) { + this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled; + this._captchaEntryTextbox.disabled = disabled; + this._captchaRefreshButton.disabled = disabled; + } + + _captchaGuessIsEmpty() { + return this._captchaEntryTextbox.value == ""; + } + + init(window, dialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, dialog); + }, 0); + } + + close() { + BridgeDB.close(); + } + + /* + Event Handlers + */ + onSubmitCaptcha() { + let captchaText = this._captchaEntryTextbox.value.trim(); + // noop if the field is empty + if (captchaText == "") { + return; + } + + // freeze ui while we make request + this._setUIDisabled(true); + this._incorrectCaptchaHbox.style.visibility = "hidden"; + + BridgeDB.submitCaptchaGuess(captchaText) + .then(aBridges => { + if (aBridges) { + this._bridges = aBridges; + this._submitButton.disabled = false; + // This was successful, but use cancelDialog() to close, since + // we intercept the `dialogaccept` event. + this._dialog.cancelDialog(); + } else { + this._bridges = []; + this._setUIDisabled(false); + this._incorrectCaptchaHbox.style.visibility = "visible"; + } + }) + .catch(aError => { + // TODO: handle other errors properly here when we do the bridge settings re-design + this._bridges = []; + this._setUIDisabled(false); + this._incorrectCaptchaHbox.style.visibility = "visible"; + console.log(aError); + }); + } + + onRefreshCaptcha() { + this._setUIDisabled(true); + this._captchaImage.src = ""; + this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB; + this._captchaEntryTextbox.value = ""; + this._incorrectCaptchaHbox.style.visibility = "hidden"; + + BridgeDB.requestNewCaptchaImage().then(uri => { + this._setcaptchaImage(uri); + }); + } + + openDialog(gSubDialog, aCloseCallback) { + gSubDialog.open( + "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml", + { + features: "resizable=yes", + closingCallback: () => { + this.close(); + aCloseCallback(this._bridges); + }, + }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml new file mode 100644 index 0000000000000..b7286528a8a5a --- /dev/null +++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-requestBridge-dialog" + buttons="help,accept,cancel"> + <!-- ok, so ​ is a zero-width space. We need to have *something* in the innerText so that XUL knows how tall the + title node is so that it can determine how large to make the dialog element's inner draw area. If we have nothing + in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( --> + <html:h3 id="torPreferences-requestBridge-header">​</html:h3> + <!-- init to transparent 400x125 png --> + <image id="torPreferences-requestBridge-captchaImage" flex="1"/> + <hbox id="torPreferences-requestBridge-inputHbox"> + <html:input id="torPreferences-requestBridge-captchaTextbox" type="text" style="-moz-box-flex: 1;"/> + <button id="torPreferences-requestBridge-refreshCaptchaButton" + image="chrome://browser/skin/reload.svg" + oncommand="requestBridgeDialog.onRefreshCaptcha();"/> + </hbox> + <hbox id="torPreferences-requestBridge-incorrectCaptchaHbox" align="center"> + <image id="torPreferences-requestBridge-errorIcon" /> + <label id="torPreferences-requestBridge-incorrectCaptchaError" flex="1"/> + </hbox> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let requestBridgeDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-requestBridge-dialog"); + requestBridgeDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm new file mode 100644 index 0000000000000..94a57b9b165ee --- /dev/null +++ b/browser/components/torpreferences/content/torLogDialog.jsm @@ -0,0 +1,84 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["TorLogDialog"]; + +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +class TorLogDialog { + constructor() { + this._dialog = null; + this._logTextarea = null; + this._copyLogButton = null; + this._restoreButtonTimeout = null; + } + + static get selectors() { + return { + copyLogButton: "extra1", + logTextarea: "textarea#torPreferences-torDialog-textarea", + }; + } + + _populateXUL(aDialog) { + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + dialogWin.setAttribute("title", TorStrings.settings.torLogDialogTitle); + + this._logTextarea = this._dialog.querySelector( + TorLogDialog.selectors.logTextarea + ); + + this._copyLogButton = this._dialog.getButton( + TorLogDialog.selectors.copyLogButton + ); + this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog); + this._copyLogButton.addEventListener("command", () => { + this.copyTorLog(); + const label = this._copyLogButton.querySelector("label"); + label.setAttribute("value", TorStrings.settings.copied); + this._copyLogButton.classList.add("primary"); + + const RESTORE_TIME = 1200; + if (this._restoreButtonTimeout !== null) { + clearTimeout(this._restoreButtonTimeout); + } + this._restoreButtonTimeout = setTimeout(() => { + label.setAttribute("value", TorStrings.settings.copyLog); + this._copyLogButton.classList.remove("primary"); + this._restoreButtonTimeout = null; + }, RESTORE_TIME); + }); + + this._logTextarea.value = TorProtocolService.getLog(); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(aDialog); + }, 0); + } + + copyTorLog() { + // Copy tor log messages to the system clipboard. + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( + Ci.nsIClipboardHelper + ); + clipboard.copyString(this._logTextarea.value); + } + + openDialog(gSubDialog) { + gSubDialog.open( + "chrome://browser/content/torpreferences/torLogDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/torLogDialog.xhtml b/browser/components/torpreferences/content/torLogDialog.xhtml new file mode 100644 index 0000000000000..9c17f8132978d --- /dev/null +++ b/browser/components/torpreferences/content/torLogDialog.xhtml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-torLog-dialog" + buttons="accept,extra1"> + <html:textarea + id="torPreferences-torDialog-textarea" + multiline="true" + readonly="true"/> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let torLogDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-torLog-dialog"); + torLogDialog.init(window, dialog); + ]]></script> +</dialog> +</window> \ No newline at end of file diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css new file mode 100644 index 0000000000000..31b6e29d679f3 --- /dev/null +++ b/browser/components/torpreferences/content/torPreferences.css @@ -0,0 +1,541 @@ +@import url("chrome://branding/content/tor-styles.css"); + +#category-connection > .category-icon { + list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg"); +} + +html:dir(rtl) input[type="checkbox"].toggle-button::before { + /* For some reason, the rule from toggle-button.css is not applied... */ + scale: -1; +} + +/* Connect Message Box */ + +#torPreferences-connectMessageBox { + display: block; + position: relative; + + width: auto; + min-height: 32px; + border-radius: 4px; + padding: 8px; +} + +#torPreferences-connectMessageBox.hidden { + display: none; +} + +#torPreferences-connectMessageBox.error { + background-color: var(--red-60); + color: white; +} + +#torPreferences-connectMessageBox.warning { + background-color: var(--purple-50); + color: white; +} + +#torPreferences-connectMessageBox table { + border-collapse: collapse; +} + +#torPreferences-connectMessageBox td { + vertical-align: middle; +} + +#torPreferences-connectMessageBox td:first-child { + width: 16px; +} + +#torPreferences-connectMessageBox-icon { + width: 16px; + height: 16px; + + mask-repeat: no-repeat !important; + mask-size: 16px !important; +} + +#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-icon +{ + mask: url("chrome://browser/skin/onion-slash.svg"); + background-color: white; +} + +#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-icon +{ + mask: url("chrome://browser/skin/onion.svg"); + background-color: white; +} + +#torPreferences-connectMessageBox-message { + line-height: 16px; + padding-inline-start: 8px; +} + +#torPreferences-connectMessageBox-button { + display: block; + width: auto; + + border-radius: 4px; + border: 0; + + padding-inline: 18px; + padding-block: 8px; + margin-block: 0px; + margin-inline-start: 8px; + margin-inline-end: 0px; + + font-size: 1.0em; + font-weight: 600; + white-space: nowrap; + + color: white; +} + +#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button { + background-color: var(--red-70); +} + +#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:hover { + background-color: var(--red-80); +} + +#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:active { + background-color: var(--red-90); +} + +#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button { + background-color: var(--purple-70); +} + +#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:hover { + background-color: var(--purple-80); +} + +#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:active { + background-color: var(--purple-90); +} + +/* Status */ +#torPreferences-status-box { + display: flex; + align-items: center; +} + +#torPreferences-status-internet-icon, #torPreferences-status-tor-icon { + width: 18px; + height: 18px; + margin-inline-end: 8px; +} + +#torPreferences-status-internet-label, #torPreferences-status-tor-label { + font-weight: bold; +} + +#torPreferences-status-internet-icon { + list-style-image: url("chrome://devtools/skin/images/globe.svg"); +} + +#torPreferences-status-internet-statusIcon.online, +#torPreferences-status-internet-statusIcon.offline, +#torPreferences-status-tor-statusIcon { + margin-inline-start: 12px; + margin-inline-end: 9px; +} + +#torPreferences-status-internet-statusIcon.online, #torPreferences-status-tor-statusIcon.connected { + list-style-image: url("chrome://devtools/skin/images/check.svg"); + -moz-context-properties: fill; + fill: var(--purple-60); +} + +#torPreferences-status-internet-status { + margin-inline-end: 32px; +} + +#torPreferences-status-tor-icon { + list-style-image: url("chrome://browser/skin/onion.svg"); +} + +#torPreferences-status-internet-icon, #torPreferences-status-tor-icon { + -moz-context-properties: fill; + fill: var(--in-content-text-color); +} + +#torPreferences-status-tor-statusIcon, #torPreferences-status-internet-statusIcon.offline { + list-style-image: url("chrome://browser/skin/warning.svg"); +} + +#torPreferences-status-tor-statusIcon.blocked { + -moz-context-properties: fill; + fill: var(--red-60); +} + +/* Bridge settings */ +#torPreferences-bridges-location { + width: 280px; +} + +/* Bridge cards */ +:root { + --bridgeCard-animation-time: 0.25s; +} + +#torPreferences-currentBridges-cards { + /* The padding is needed because the mask-image creates an unexpected result + otherwise... */ + padding: 24px 4px; +} + +#torPreferences-currentBridges-cards.list-collapsed { + mask-image: linear-gradient(rgb(0, 0, 0), rgba(0, 0, 0, 0.1)); +} + +#torPreferences-currentBridges-cards.disabled { + opacity: 0.4; +} + +.torPreferences-bridgeCard { + padding: 16px 12px; + /* define border-radius here because of the transition */ + border-radius: 4px; + transition: margin var(--bridgeCard-animation-time), box-shadow 150ms; +} + +.torPreferences-bridgeCard.expanded { + margin: 12px 0; + background: var(--in-content-box-background); + box-shadow: var(--card-shadow); +} + +.torPreferences-bridgeCard:hover { + background: var(--in-content-box-background); + box-shadow: var(--card-shadow-hover); + cursor: pointer; +} + +.torPreferences-bridgeCard-heading { + display: flex; + align-items: center; +} + +.torPreferences-bridgeCard-id { + font-weight: 700; +} + +.torPreferences-bridgeCard-id .emoji { + margin-inline-start: 4px; + padding: 4px; + font-size: 20px; + border-radius: 4px; + background: var(--in-content-button-background-hover); +} + +.torPreferences-bridgeCard-headingAddr { + /* flex extends the element when needed, but without setting a width (any) the + overflow + ellipses does not work. */ + width: 20px; + flex: 1; + margin: 0 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.expanded .torPreferences-bridgeCard-headingAddr { + display: none; +} + +.torPreferences-bridgeCard-buttons { + display: flex; + align-items: center; + margin-inline-start: auto; + align-self: center; +} + +.torPreferences-bridgeCard-connectedBadge { + display: none; + padding: 8px 12px; + border-radius: 16px; + background: rgba(128, 0, 215, 0.1); + color: var(--purple-60); +} + +.currently-connected .torPreferences-bridgeCard-connectedBadge { + display: flex; +} + +.torPreferences-bridgeCard-connectedIcon { + margin-inline-start: 1px; + margin-inline-end: 7px; + list-style-image: url("chrome://devtools/skin/images/check.svg"); + -moz-context-properties: fill; + fill: var(--purple-60); +} + +.torPreferences-bridgeCard-options { + width: 24px; + min-width: 0; + height: 24px; + min-height: 0; + margin-inline-start: 8px; + padding: 1px; + background-image: url("chrome://global/skin/icons/more.svg"); + background-repeat: no-repeat; + background-position: center center; + fill: currentColor; + -moz-context-properties: fill; +} + +.torPreferences-bridgeCard-qrWrapper { + grid-area: bridge-qr; + display: flex; + flex-direction: column; +} + +.torPreferences-bridgeCard-qr { + width: 126px; + position: relative; +} + +.torPreferences-bridgeCard-qrCode { + width: 112px; + height: 112px; + /* Define these colors, as they will be passed to the QR code library */ + background: var(--in-content-box-background); + color: var(--in-content-text-color); +} + +.torPreferences-bridgeCard-qrOnionBox { + width: 28px; + height: 28px; + position: absolute; + top: 42px; + inset-inline-start: 42px; + background: var(--in-content-box-background); +} + +.torPreferences-bridgeCard-qrOnion { + width: 16px; + height: 16px; + position: absolute; + top: 48px; + inset-inline-start: 48px; + + mask: url("chrome://browser/skin/onion.svg"); + mask-repeat: no-repeat; + mask-size: 16px; + background: var(--in-content-text-color); +} + +.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnionBox { + background: var(--in-content-text-color); +} + +.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnion { + mask: url("chrome://global/skin/icons/search-glass.svg"); + background: var(--in-content-box-background); +} + +.torPreferences-bridgeCard-filler { + flex: 1; +} + +.torPreferences-bridgeCard-grid { + height: 0; /* We will set it in JS when expanding it! */ + display: grid; + grid-template-rows: auto 1fr; + grid-template-columns: auto 1fr auto; + grid-template-areas: + 'bridge-qr bridge-share bridge-share' + 'bridge-qr bridge-address bridge-address' + 'bridge-qr bridge-learn-more bridge-copy'; + padding-top: 12px; + visibility: hidden; +} + +.expanded .torPreferences-bridgeCard-grid { + visibility: visible; +} + +.torPreferences-bridgeCard-grid.to-animate { + transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time); + overflow: hidden; +} + +.torPreferences-bridgeCard-share { + grid-area: bridge-share; +} + +.torPreferences-bridgeCard-addrBox { + grid-area: bridge-address; + display: flex; + align-items: center; + justify-content: center; + margin: 8px 0; +} + +.torPreferences-bridgeCard-addr { + width: 100%; +} + +.torPreferences-bridgeCard-leranMoreBox { + grid-area: bridge-learn-more; +} + +.torPreferences-bridgeCard-copy { + grid-area: bridge-copy; +} + +#torPreferences-bridgeCard-template { + display: none; +} + +/* Advanced Settings */ +#torPreferences-advanced-grid { + display: grid; + grid-template-columns: 1fr auto; +} + +#torPreferences-advanced-group button { + min-width: 150px; +} + +#torPreferences-advanced-hbox, #torPreferences-torDaemon-hbox { + padding-inline-end: 15px; +} + +h3#torPreferences-requestBridge-header { + margin: 0; +} + +image#torPreferences-requestBridge-captchaImage { + margin: 16px 0 8px 0; + min-height: 140px; +} + +button#torPreferences-requestBridge-refreshCaptchaButton { + min-width: initial; +} + +dialog#torPreferences-requestBridge-dialog > hbox { + margin-bottom: 1em; +} + +/* + Various elements that really should be lining up don't because they have inconsistent margins +*/ +.torMarginFix { + margin-left : 4px; + margin-right : 4px; +} + +/* Show bridge QR dialog */ +#bridgeQr-container { + position: relative; + height: 300px; +} + +#bridgeQr-target { + position: absolute; + width: 300px; + height: 300px; + left: calc(50% - 150px); + background: var(--in-content-box-background); + color: var(--in-content-text-color); +} + +#bridgeQr-onionBox { + position: absolute; + width: 70px; + height: 70px; + top: 115px; + left: calc(50% - 35px); + background-color: var(--in-content-box-background); +} + +#bridgeQr-onion { + position: absolute; + width: 38px; + height: 38px; + top: 131px; + left: calc(50% - 19px); + mask: url("chrome://browser/skin/onion.svg"); + mask-repeat: no-repeat; + mask-size: 38px; + background: var(--in-content-text-color); +} + +/* Builtin bridge dialog */ +#torPreferences-builtinBridge-header { + margin: 8px 0 10px 0; +} + +#torPreferences-builtinBridge-description { + margin-bottom: 18px; +} + +#torPreferences-builtinBridge-typeSelection { + margin-bottom: 16px; + min-height: 14em; /* Hack: make room for at least 4 lines of content for 3 types + 2 for spacing */ +} + +#torPreferences-builtinBridge-typeSelection radio label { + font-weight: 700; +} + +/* Request bridge dialog */ +/* + This hbox is hidden by css here by default so that the + xul dialog allocates enough screen space for the error message + element, otherwise it gets cut off since dialog's overflow is hidden +*/ +hbox#torPreferences-requestBridge-incorrectCaptchaHbox { + visibility: hidden; +} + +image#torPreferences-requestBridge-errorIcon { + list-style-image: url("chrome://browser/skin/warning.svg"); +} + +groupbox#torPreferences-bridges-group textarea { + white-space: pre; + overflow: auto; +} + +/* Provide bridge dialog */ +#torPreferences-provideBridge-header { + margin-top: 8px; +} + +/* Connection settings dialog */ +#torPreferences-connection-header { + margin: 4px 0 14px 0; +} + +#torPreferences-connection-grid { + display: grid; + grid-template-columns: auto 1fr; +} + +.torPreferences-connection-checkbox-container { + grid-column: 1 / 3; +} + +#torPreferences-localProxy-textboxAddress, +#torPreferences-localProxy-textboxUsername, +#torPreferences-localProxy-textboxPassword, +#torPreferences-connection-textboxAllowedPorts { + -moz-box-flex: 1; +} + +/* Tor logs dialog */ +textarea#torPreferences-torDialog-textarea { + -moz-box-flex: 1; + font-family: monospace; + font-size: 0.8em; + white-space: pre; + overflow: auto; + /* 10 lines */ + min-height: 20em; +} diff --git a/browser/components/torpreferences/content/torPreferencesIcon.svg b/browser/components/torpreferences/content/torPreferencesIcon.svg new file mode 100644 index 0000000000000..382a061774aaa --- /dev/null +++ b/browser/components/torpreferences/content/torPreferencesIcon.svg @@ -0,0 +1,8 @@ +<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <g clip-rule="evenodd" fill-rule="evenodd"> + <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/> + <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/> + <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/> + </g> + <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/> +</svg> \ No newline at end of file diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn new file mode 100644 index 0000000000000..ed3bb441084c9 --- /dev/null +++ b/browser/components/torpreferences/jar.mn @@ -0,0 +1,19 @@ +browser.jar: + content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml) + content/browser/torpreferences/bridgeQrDialog.jsm (content/bridgeQrDialog.jsm) + content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml) + content/browser/torpreferences/builtinBridgeDialog.jsm (content/builtinBridgeDialog.jsm) + content/browser/torpreferences/connectionSettingsDialog.xhtml (content/connectionSettingsDialog.xhtml) + content/browser/torpreferences/connectionSettingsDialog.jsm (content/connectionSettingsDialog.jsm) + content/browser/torpreferences/network.svg (content/network.svg) + content/browser/torpreferences/provideBridgeDialog.xhtml (content/provideBridgeDialog.xhtml) + content/browser/torpreferences/provideBridgeDialog.jsm (content/provideBridgeDialog.jsm) + content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml) + content/browser/torpreferences/requestBridgeDialog.jsm (content/requestBridgeDialog.jsm) + content/browser/torpreferences/connectionCategory.inc.xhtml (content/connectionCategory.inc.xhtml) + content/browser/torpreferences/torLogDialog.jsm (content/torLogDialog.jsm) + content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml) + content/browser/torpreferences/connectionPane.js (content/connectionPane.js) + content/browser/torpreferences/connectionPane.xhtml (content/connectionPane.xhtml) + content/browser/torpreferences/torPreferences.css (content/torPreferences.css) + content/browser/torpreferences/torPreferencesIcon.svg (content/torPreferencesIcon.svg) diff --git a/browser/components/torpreferences/moz.build b/browser/components/torpreferences/moz.build new file mode 100644 index 0000000000000..2661ad7cb9f3d --- /dev/null +++ b/browser/components/torpreferences/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"]
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 6bb230b225baf1a79439324a20fb8506d6d899b3 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Fri Apr 8 11:20:37 2022 +0200
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Changes introduced by !275 --- .../torpreferences/content/connectionPane.js | 402 ++++++--------------- .../torpreferences/content/connectionPane.xhtml | 17 + .../torpreferences/content/torPreferences.css | 130 ++++++- 3 files changed, 251 insertions(+), 298 deletions(-)
diff --git a/browser/components/torpreferences/content/connectionPane.js b/browser/components/torpreferences/content/connectionPane.js index 309d6498a0c80..46fbbfecf832f 100644 --- a/browser/components/torpreferences/content/connectionPane.js +++ b/browser/components/torpreferences/content/connectionPane.js @@ -110,6 +110,7 @@ const gConnectionPane = (function() { chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe", currentHeader: "#torPreferences-currentBridges-header", currentHeaderText: "#torPreferences-currentBridges-headerText", + currentDescriptionText: "#torPreferences-currentBridges-description", switch: "#torPreferences-currentBridges-switch", cards: "#torPreferences-currentBridges-cards", cardTemplate: "#torPreferences-bridgeCard-template", @@ -135,6 +136,13 @@ const gConnectionPane = (function() { requestButton: "#torPreferences-addBridge-buttonRequestBridge", enterLabel: "#torPreferences-addBridge-labelEnterBridge", enterButton: "#torPreferences-addBridge-buttonEnterBridge", + removeOverlay: "#bridge-remove-overlay", + removeModal: "#bridge-remove-modal", + removeDismiss: "#bridge-remove-dismiss", + removeQuestion: "#bridge-remove-question", + removeWarning: "#bridge-remove-warning", + removeConfirm: "#bridge-remove-confirm", + removeCancel: "#bridge-remove-cancel", }, advanced: { header: "h1#torPreferences-advanced-header", @@ -280,9 +288,7 @@ const gConnectionPane = (function() { if (TorConnect.state === TorConnectState.Bootstrapped) { torIcon.className = "connected"; torStatus.textContent = TorStrings.settings.statusTorConnected; - } else if ( - TorConnect.detectedCensorshipLevel > TorCensorshipLevel.None - ) { + } else if (TorConnect.hasBootstrapEverFailed) { torIcon.className = "blocked"; torStatus.textContent = TorStrings.settings.statusTorBlocked; } else { @@ -346,36 +352,46 @@ const gConnectionPane = (function() { TorConnect.beginAutoBootstrap(location.value); }); this._populateLocations = () => { - let value = location.value; + const currentValue = location.value; locationEntries.textContent = ""; - - { - const item = document.createXULElement("menuitem"); - item.setAttribute("value", ""); - item.setAttribute( - "label", - TorStrings.settings.bridgeLocationAutomatic - ); - locationEntries.appendChild(item); - } - - const codes = TorConnect.countryCodes; - const items = codes.map(code => { + const createItem = (value, label, disabled) => { const item = document.createXULElement("menuitem"); - item.setAttribute("value", code); - item.setAttribute( - "label", - TorConnect.countryNames[code] - ? TorConnect.countryNames[code] - : code - ); + item.setAttribute("value", value); + item.setAttribute("label", label); + if (disabled) { + item.setAttribute("disabled", "true"); + } return item; - }); - items.sort((left, right) => - left.textContent.localeCompare(right.textContent) + }; + const addLocations = codes => { + const items = []; + for (const code of codes) { + items.push( + createItem( + code, + TorConnect.countryNames[code] + ? TorConnect.countryNames[code] + : code + ) + ); + } + items.sort((left, right) => left.label.localeCompare(right.label)); + locationEntries.append(...items); + }; + locationEntries.append( + createItem("", TorStrings.settings.bridgeLocationAutomatic) ); - locationEntries.append(...items); - location.value = value; + if (TorConnect.countryCodes.length) { + locationEntries.append( + createItem("", TorStrings.settings.bridgeLocationFrequent, true) + ); + addLocations(TorConnect.countryCodes); + locationEntries.append( + createItem("", TorStrings.settings.bridgeLocationOther, true) + ); + } + addLocations(Object.keys(TorConnect.countryNames)); + location.value = currentValue; }; this._showAutoconfiguration = () => { if ( @@ -413,6 +429,9 @@ const gConnectionPane = (function() { this._populateBridgeCards(); }); }); + prefpane.querySelector( + selectors.bridges.currentDescriptionText + ).textContent = TorStrings.settings.bridgeCurrentDescription; const bridgeTemplate = prefpane.querySelector( selectors.bridges.cardTemplate ); @@ -439,6 +458,12 @@ const gConnectionPane = (function() { card.removeAttribute("id"); const grid = card.querySelector(selectors.bridges.cardQrGrid); card.addEventListener("click", e => { + if ( + card.classList.contains("currently-connected") || + bridgeCards.classList.contains("single-card") + ) { + return; + } let target = e.target; let apply = true; while (target !== null && target !== card && apply) { @@ -598,7 +623,7 @@ const gConnectionPane = (function() { const removeAll = prefpane.querySelector(selectors.bridges.removeAll); removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll); removeAll.addEventListener("command", () => { - this.onRemoveAllBridges(); + this._confirmBridgeRemoval(); }); this._populateBridgeCards = async () => { const collapseThreshold = 4; @@ -617,6 +642,7 @@ const gConnectionPane = (function() { bridgeCards.removeAttribute("hidden"); bridgeSwitch.checked = TorSettings.bridges.enabled; bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled); + bridgeCards.classList.toggle("single-card", numBridges === 1);
let shownCards = 0; const toShow = this._currentBridgesExpanded @@ -787,7 +813,37 @@ const gConnectionPane = (function() { }); }
- Services.obs.addObserver(this, TorConnectTopics.StateChange); + { + const overlay = prefpane.querySelector(selectors.bridges.removeOverlay); + this._confirmBridgeRemoval = () => { + overlay.classList.remove("hidden"); + }; + const closeDialog = () => { + overlay.classList.add("hidden"); + }; + overlay.addEventListener("click", closeDialog); + const modal = prefpane.querySelector(selectors.bridges.removeModal); + modal.addEventListener("click", e => { + e.stopPropagation(); + }); + const dismiss = prefpane.querySelector(selectors.bridges.removeDismiss); + dismiss.addEventListener("click", closeDialog); + const question = prefpane.querySelector( + selectors.bridges.removeQuestion + ); + question.textContent = TorStrings.settings.removeBridgesQuestion; + const warning = prefpane.querySelector(selectors.bridges.removeWarning); + warning.textContent = TorStrings.settings.removeBridgesWarning; + const confirm = prefpane.querySelector(selectors.bridges.removeConfirm); + confirm.setAttribute("label", TorStrings.settings.remove); + confirm.addEventListener("command", () => { + this.onRemoveAllBridges(); + closeDialog(); + }); + const cancel = prefpane.querySelector(selectors.bridges.removeCancel); + cancel.setAttribute("label", TorStrings.settings.cancel); + cancel.addEventListener("command", closeDialog); + }
// Advanced setup prefpane.querySelector(selectors.advanced.header).innerText = @@ -816,6 +872,8 @@ const gConnectionPane = (function() { torLogsButton.addEventListener("command", () => { this.onViewTorLogs(); }); + + Services.obs.addObserver(this, TorConnectTopics.StateChange); },
init() { @@ -915,6 +973,9 @@ const gConnectionPane = (function() { onRemoveAllBridges() { TorSettings.bridges.enabled = false; TorSettings.bridges.bridge_strings = ""; + if (TorSettings.bridges.source == TorBridgeSource.BuiltIn) { + TorSettings.bridges.builtin_type = ""; + } TorSettings.saveToPrefs(); TorSettings.applySettings().then(result => { this._populateBridgeCards(); @@ -1018,272 +1079,33 @@ function makeBridgeId(bridgeString) { // JS uses UTF-16. While most of these emojis are surrogate pairs, a few // ones fit one UTF-16 character. So we could not use neither indices, // nor substr, nor some function to split the string. - const emojis = [ - "😄️", - "😒️", - "😉", - "😭️", - "😂️", - "😎️", - "🤩️", - "😘", - "😜️", - "😏️", - "😷", - "🤢", - "🤕", - "🤧", - "🥵", - "🥶", - "🥴", - "😵️", - "🤮️", - "🤑", - "🤔", - "🫢", - "🤐", - "😮💨", - "😐", - "🤤", - "😴", - "🤯", - "🤠", - "🥳", - "🥸", - "🤓", - "🧐", - "😨", - "😳", - "🥺", - "🤬", - "😈", - "👿", - "💀", - "💩", - "🤡", - "👺", - "👻", - "👽", - "🦴", - "🤖", - "😸", - "🙈", - "🙉", - "🙊", - "💋", - "💖", - "💯", - "💢", - "💧", - "💨", - "💭", - "💤", - "👋", - "👌", - "✌", - "👍", - "👎", - "🤛", - "🙌", - "💪", - "🙏", - "✍", - "🧠", - "👀", - "👂", - "👅", - "🦷", - "🐾", - "🐶", - "🦊", - "🦝", - "🐈", - "🦁", - "🐯", - "🐴", - "🦄", - "🦓", - "🐮", - "🐷", - "🐑", - "🐪", - "🐘", - "🐭", - "🐰", - "🦔", - "🦇", - "🐻", - "🐨", - "🐼", - "🐔", - "🦨", - "🦘", - "🐦", - "🐧", - "🦩", - "🦉", - "🦜", - "🪶", - "🐸", - "🐊", - "🐢", - "🦎", - "🐍", - "🦖", - "🦀", - "🐬", - "🐙", - "🐌", - "🐝", - "🐞", - "🌸", - "🌲", - "🌵", - "🍀", - "🍁", - "🍇", - "🍉", - "🍊", - "🍋", - "🍌", - "🍍", - "🍎", - "🥥", - "🍐", - "🍒", - "🍓", - "🫐", - "🥝", - "🥔", - "🥕", - "🧅", - "🌰", - "🍄", - "🍞", - "🥞", - "🧀", - "🍖", - "🍔", - "🍟", - "🍕", - "🥚", - "🍿", - "🧂", - "🍙", - "🍦", - "🍩", - "🍪", - "🎂", - "🍬", - "🍭", - "🥛", - "☕", - "🫖", - "🍾", - "🍷", - "🍹", - "🍺", - "🍴", - "🥄", - "🫙", - "🧭", - "🌋", - "🪵", - "🏡", - "🏢", - "🏰", - "⛲", - "⛺", - "🎡", - "🚂", - "🚘", - "🚜", - "🚲", - "🚔", - "🚨", - "⛽", - "🚥", - "🚧", - "⚓", - "⛵", - "🛟", - "🪂", - "🚀", - "⌛", - "⏰", - "🌂", - "🌞", - "🌙", - "🌟", - "⛅", - "⚡", - "🔥", - "🌊", - "🎃", - "🎈", - "🎉", - "✨", - "🎀", - "🎁", - "🏆", - "🏅", - "🔮", - "🪄", - "🎾", - "🎳", - "🎲", - "🎭", - "🎨", - "🧵", - "🎩", - "📢", - "🔔", - "🎵", - "🎤", - "🎧", - "🎷", - "🎸", - "🥁", - "🔋", - "🔌", - "💻", - "💾", - "💿", - "🎬", - "📺", - "📷", - "🎮", - "🧩", - "🔍", - "💡", - "📖", - "💰", - "💼", - "📈", - "📌", - "📎", - "🔒", - "🔑", - "🔧", - "🪛", - "🔩", - "🧲", - "🔬", - "🔭", - "📡", - "🚪", - "🪑", - "⛔", - "🚩", +const emojis = [ + "👽","🤖","🧚","🧜","🏄","🐵","🦍","🐶","🐺","🦊","🐈","🦁","🐯","🐴","🦄","🦓", + "🦌","🐮","🐷","🐗","🐑","🦙","🦒","🐘","🐭","🐹","🐇","🐿","🦔","🐨","🐼","🦥", + "🦨","🦘","🐓","🐥","🐦","🐧","🕊","🦆","🦢","🦉","🦤","🦩","🦚","🦜","🐊","🐢", + "🦎","🐍","🐉","🦕","🦖","🐋","🐬","🐟","🐠","🐡","🦈","🐙","🐚","🐌","🦋","🐛", + "🐝","🐞","💐","🌸","🌹","🌺","🌻","🌼","🌷","🌱","🌲","🌳","🌴","🌵","🌿","🍁", + "🍇","🍉","🍊","🍋","🍌","🍍","🥭","🍏","🍐","🍑","🍒","🍓","🥝","🍅","🥥","🥑", + "🍆","🥕","🌽","🌶","🥬","🥦","🧅","🍄","🥜","🥐","🥖","🥨","🥞","🧇","🍔","🍕", + "🌭","🌮","🌯","🥚","🍿","🍙","🥟","🦀","🦞","🦑","🍦","🍩","🧁","🍬","🍭","🧃", + "🧉","🧭","⛰","🌋","🏝","🏡","⛲","⛺","🎠","🎡","💈","🚂","🚃","🚌","🚗","🚚", + "🚜","🛵","🛺","🚲","🛴","🛹","⚓️","⛵","🛶","🚤","🚢","✈️","🪂","🚁","🚠","🛰", + "🚀","🛸","⏳","🌙","🌡","☀️","🪐","⭐","☁️","🌧","🌩","🌀","🌈","☂️","❄️","☄️", + "🔥","💧","🌊","🎃","✨","🎈","🎉","🎊","🎏","🎟","🏆","⚽","🏀","🏈","🎾","🥏", + "🏓","⛸","🪀","🪁","🎱","🔮","🪄","🕹","🎲","🧩","🧸","🎨","🧵","🧶","🕶","🧦", + "🎒","👟","👠","👑","🎓","🧢","💍","💎","📢","🎵","🎙","🎤","🎧","📻","🎷","🪗", + "🎸","🎺","🎻","🪕","🥁","☎️","💿","🎥","🎬","📺","📷","🔍","💡","🔦","📖","📚", + "🏷","✏️","🖌","🖍","📎","📌","🔑","🪃","🏹","⚙️","🧲","🧪","🧬","🔭","📡","🗿", ];
+ // FNV-1a implementation that is compatible with other languages const prime = 0x01000193; const offset = 0x811c9dc5; let hash = offset; const encoder = new TextEncoder(); - for (const charCode of encoder.encode(bridgeString)) { - hash = Math.imul(hash ^ charCode, prime); + for (const byte of encoder.encode(bridgeString)) { + hash = Math.imul(hash ^ byte, prime); }
const hashBytes = [ diff --git a/browser/components/torpreferences/content/connectionPane.xhtml b/browser/components/torpreferences/content/connectionPane.xhtml index 67f98685d8038..39a9c184502f0 100644 --- a/browser/components/torpreferences/content/connectionPane.xhtml +++ b/browser/components/torpreferences/content/connectionPane.xhtml @@ -93,6 +93,9 @@ <html:span id="torPreferences-currentBridges-headerText"/> <html:input type="checkbox" id="torPreferences-currentBridges-switch" class="toggle-button"/> </html:h2> + <description flex="1"> + <html:span id="torPreferences-currentBridges-description"/> + </description> <menupopup id="torPreferences-bridgeCard-menu"/> <vbox id="torPreferences-bridgeCard-template" class="torPreferences-bridgeCard"> <hbox class="torPreferences-bridgeCard-heading"> @@ -174,4 +177,18 @@ </hbox> </box> </groupbox> + +<html:div id="bridge-remove-overlay" class="hidden"> + <html:div id="bridge-remove-modal"> + <html:img id="bridge-remove-dismiss" src="chrome://global/skin/icons/close.svg"/> + <html:div id="bridge-remove-icon"/> + <html:p id="bridge-remove-question"/> + <html:p id="bridge-remove-warning"/> + <html:div id="bridge-remove-buttonbar"> + <button id="bridge-remove-cancel"/> + <button id="bridge-remove-confirm"/> + </html:div> + </html:div> +</html:div> + </html:template> diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css index 31b6e29d679f3..2ab29bcd60faa 100644 --- a/browser/components/torpreferences/content/torPreferences.css +++ b/browser/components/torpreferences/content/torPreferences.css @@ -176,6 +176,11 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { width: 280px; }
+#torPreferences-bridges-location menuitem[disabled="true"] { + color: var(--in-content-button-text-color, inherit); + font-weight: 700; +} + /* Bridge cards */ :root { --bridgeCard-animation-time: 0.25s; @@ -188,7 +193,7 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { }
#torPreferences-currentBridges-cards.list-collapsed { - mask-image: linear-gradient(rgb(0, 0, 0), rgba(0, 0, 0, 0.1)); + mask-image: linear-gradient(rgb(0, 0, 0) 0% 75%, rgba(0, 0, 0, 0.1)); }
#torPreferences-currentBridges-cards.disabled { @@ -200,9 +205,12 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { /* define border-radius here because of the transition */ border-radius: 4px; transition: margin var(--bridgeCard-animation-time), box-shadow 150ms; + cursor: pointer; }
-.torPreferences-bridgeCard.expanded { +.torPreferences-bridgeCard.expanded, +.torPreferences-bridgeCard.currently-connected, +.single-card .torPreferences-bridgeCard { margin: 12px 0; background: var(--in-content-box-background); box-shadow: var(--card-shadow); @@ -211,7 +219,11 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { .torPreferences-bridgeCard:hover { background: var(--in-content-box-background); box-shadow: var(--card-shadow-hover); - cursor: pointer; +} + +.single-card .torPreferences-bridgeCard, +.torPreferences-bridgeCard.currently-connected { + cursor: default; }
.torPreferences-bridgeCard-heading { @@ -228,7 +240,7 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { padding: 4px; font-size: 20px; border-radius: 4px; - background: var(--in-content-button-background-hover); + background: var(--in-content-box-background-odd); }
.torPreferences-bridgeCard-headingAddr { @@ -237,12 +249,15 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { width: 20px; flex: 1; margin: 0 8px; - white-space: nowrap; overflow: hidden; + color: var(--in-content-deemphasized-text); + white-space: nowrap; text-overflow: ellipsis; }
-.expanded .torPreferences-bridgeCard-headingAddr { +.expanded .torPreferences-bridgeCard-headingAddr, +.currently-connected .torPreferences-bridgeCard-headingAddr, +.single-card .torPreferences-bridgeCard-headingAddr { display: none; }
@@ -354,10 +369,17 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { visibility: hidden; }
-.expanded .torPreferences-bridgeCard-grid { +.expanded .torPreferences-bridgeCard-grid, +.currently-connected .torPreferences-bridgeCard-grid, +.single-card .torPreferences-bridgeCard-grid { visibility: visible; }
+.currently-connected .torPreferences-bridgeCard-grid, +.single-card .torPreferences-bridgeCard-grid { + height: auto; +} + .torPreferences-bridgeCard-grid.to-animate { transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time); overflow: hidden; @@ -375,8 +397,9 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { margin: 8px 0; }
-.torPreferences-bridgeCard-addr { +input.torPreferences-bridgeCard-addr { width: 100%; + color: var(--in-content-deemphasized-text); }
.torPreferences-bridgeCard-leranMoreBox { @@ -539,3 +562,94 @@ textarea#torPreferences-torDialog-textarea { /* 10 lines */ min-height: 20em; } + +/* Bridge remove overlay */ +#bridge-remove-overlay { + position: fixed; + display: flex; + align-items: center; + justify-content: center; + top: 0; + inset: 0; + width: 100%; + height: 100%; + z-index: 1; + background-color: rgba(0, 0, 0, 0.5); +} + +#bridge-remove-overlay.hidden { + display: none; +} + +#bridge-remove-modal { + position: relative; + min-width: 250px; + max-width: 500px; + min-height: 200px; + z-index: 2; + text-align: center; + background: var(--in-content-page-background); + box-shadow: var(--shadow-30); +} + +#bridge-remove-dismiss { + position: absolute; + top: 16px; + inset-inline-end: 16px; + width: 16px; + height: 16px; + fill: currentColor; + -moz-context-properties: fill; +} + +#bridge-remove-dismiss:hover { + background-color: var(--in-content-button-background-hover); + color: var(--in-content-button-text-color-hover); + border: 1px solid var(--in-content-button-border-color-hover); + border-radius: 4px; +} + +#bridge-remove-dismiss:hover:active { + background-color: var(--in-content-button-background-active); +} + +#bridge-remove-icon { + width: 40px; + height: 40px; + background-image: url("chrome://global/skin/icons/warning.svg"); + background-size: 40px; + margin: 16px auto; + fill: currentColor; + -moz-context-properties: fill; +} + +#bridge-remove-question { + font-size: 150%; +} + +#bridge-remove-warning { + color: var(--in-content-deemphasized-text); +} + +#bridge-remove-buttonbar { + padding: 16px 32px; +} + +#bridge-remove-buttonbar button { + min-width: 140px; +} + +#bridge-remove-confirm { + background: var(--in-content-danger-button-background); + color: var(--in-content-primary-button-text-color); +} + +#bridge-remove-confirm:hover { + background: var(--in-content-danger-button-background-hover); + color: var(--in-content-primary-button-text-color-hover); + border-color: var(--in-content-primary-button-border-hover); +} + +#bridge-remove-confirm:hover:active { + background: var(--in-content-danger-button-background-active); +}
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 1aca00cf41dfb597443284d727f8856cda9bc02c Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon May 30 14:50:55 2022 +0000
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Fixes for bugs found in 91.9, to keep as a fixup also in 91.10.
Bug 40971: Fixed the Help button on the provide bridge dialog --- .../components/torpreferences/content/provideBridgeDialog.jsm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/browser/components/torpreferences/content/provideBridgeDialog.jsm b/browser/components/torpreferences/content/provideBridgeDialog.jsm index b73a6f533afa6..2e858bc338008 100644 --- a/browser/components/torpreferences/content/provideBridgeDialog.jsm +++ b/browser/components/torpreferences/content/provideBridgeDialog.jsm @@ -22,7 +22,7 @@ class ProvideBridgeDialog { }; }
- _populateXUL(aDialog) { + _populateXUL(window, aDialog) { const selectors = ProvideBridgeDialog.selectors;
this._dialog = aDialog; @@ -45,12 +45,18 @@ class ProvideBridgeDialog { this._dialog.addEventListener("dialogaccept", e => { this._bridgeString = this._textarea.value; }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + "https://tb-manual.torproject.org/bridges/", + "tab" + ); + }); }
init(window, aDialog) { // defer to later until firefox has populated the dialog with all our elements window.setTimeout(() => { - this._populateXUL(aDialog); + this._populateXUL(window, aDialog); }, 0); }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit ae25e8f09899fb4c471271e7d2317114d2bd7b16 Author: Richard Pospesel richard@torproject.org AuthorDate: Wed Apr 28 23:09:34 2021 -0500
Bug 27476: Implement about:torconnect captive portal within Tor Browser
- implements new about:torconnect page as tor-launcher replacement - adds tor connection status to url bar and tweaks UX when not online - adds new torconnect component to browser - tor process management functionality remains implemented in tor-launcher through the TorProtocolService module - adds warning/error box to about:preferences#tor when not connected to tor - explicitly allows about:torconnect URIs to ignore Resist Fingerprinting (RFP) - various tweaks to info-pages.inc.css for about:torconnect (also affects other firefox info pages)
Bug 40773: Update the about:torconnect frontend page to match additional UI flows --- browser/actors/NetErrorParent.jsm | 8 + browser/base/content/browser-siteIdentity.js | 2 +- browser/base/content/browser.js | 66 +- browser/base/content/browser.xhtml | 2 + browser/base/content/certerror/aboutNetError.js | 12 +- browser/base/content/navigator-toolbox.inc.xhtml | 1 + browser/base/content/utilityOverlay.js | 14 + browser/components/BrowserGlue.jsm | 14 + browser/components/about/AboutRedirector.cpp | 4 + browser/components/about/components.conf | 1 + browser/components/moz.build | 1 + browser/components/sessionstore/SessionStore.jsm | 4 + browser/components/torconnect/TorConnectChild.jsm | 9 + browser/components/torconnect/TorConnectParent.jsm | 176 ++++++ .../torconnect/content/aboutTorConnect.css | 247 ++++++++ .../torconnect/content/aboutTorConnect.js | 680 +++++++++++++++++++++ .../torconnect/content/aboutTorConnect.xhtml | 66 ++ .../components/torconnect/content/arrow-right.svg | 4 + browser/components/torconnect/content/bridge.svg | 5 + .../torconnect/content/connection-failure.svg | 5 + .../torconnect/content/connection-location.svg | 5 + browser/components/torconnect/content/globe.svg | 4 + .../torconnect/content/onion-slash-fillable.svg | 5 + .../components/torconnect/content/onion-slash.svg | 5 + browser/components/torconnect/content/onion.svg | 4 + .../torconnect/content/torBootstrapUrlbar.js | 93 +++ .../torconnect/content/torconnect-urlbar.css | 57 ++ .../torconnect/content/torconnect-urlbar.inc.xhtml | 10 + browser/components/torconnect/jar.mn | 13 + browser/components/torconnect/moz.build | 6 + browser/components/urlbar/UrlbarInput.jsm | 32 + browser/modules/TorProcessService.jsm | 12 + browser/modules/moz.build | 2 + browser/themes/shared/urlbar-searchbar.inc.css | 3 + dom/base/Document.cpp | 51 +- dom/base/nsGlobalWindowOuter.cpp | 2 + toolkit/actors/AboutHttpsOnlyErrorParent.jsm | 5 + .../components/httpsonlyerror/content/errorpage.js | 19 +- .../processsingleton/MainProcessSingleton.jsm | 5 + toolkit/modules/RemotePageAccessManager.jsm | 21 + toolkit/mozapps/update/UpdateService.jsm | 68 ++- .../lib/environments/browser-window.js | 4 + 42 files changed, 1714 insertions(+), 33 deletions(-)
diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm index 3472c68f664a4..13afbbbfd4a8b 100644 --- a/browser/actors/NetErrorParent.jsm +++ b/browser/actors/NetErrorParent.jsm @@ -21,6 +21,10 @@ const { TelemetryController } = ChromeUtils.import( "resource://gre/modules/TelemetryController.jsm" );
+const { TorConnect } = ChromeUtils.import( + "resource:///modules/TorConnect.jsm" +); + const PREF_SSL_IMPACT_ROOTS = [ "security.tls.version.", "security.ssl3.", @@ -350,6 +354,10 @@ class NetErrorParent extends JSWindowActorParent { break; } } + break; + case "ShouldShowTorConnect": + return TorConnect.shouldShowTorConnect; } + return undefined; } } diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 2846a1cb2fcfd..6901ce71814a3 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -57,7 +57,7 @@ var gIdentityHandler = { * RegExp used to decide if an about url should be shown as being part of * the browser UI. */ - _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback)(?:[?#]|$)/i, + _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i,
/** * Whether the established HTTPS connection is considered "broken". diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 566976b6d7aa0..ef8a191987676 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -79,6 +79,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { TabModalPrompt: "chrome://global/content/tabprompts.jsm", TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm", + TorConnect: "resource:///modules/TorConnect.jsm", Translation: "resource:///modules/translation/TranslationParent.jsm", UITour: "resource:///modules/UITour.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", @@ -633,6 +634,7 @@ var gPageIcons = {
var gInitialPages = [ "about:tor", + "about:torconnect", "about:blank", "about:newtab", "about:home", @@ -1837,6 +1839,8 @@ var gBrowserInit = { }
this._loadHandled = true; + + TorBootstrapUrlbar.init(); },
_cancelDelayedStartup() { @@ -2385,32 +2389,48 @@ var gBrowserInit = {
let defaultArgs = BrowserHandler.defaultArgs;
- // If the given URI is different from the homepage, we want to load it. - if (uri != defaultArgs) { - AboutNewTab.noteNonDefaultStartup(); + // figure out which URI to actually load (or a Promise to get the uri) + uri = ((uri) => { + // If the given URI is different from the homepage, we want to load it. + if (uri != defaultArgs) { + AboutNewTab.noteNonDefaultStartup(); + + if (uri instanceof Ci.nsIArray) { + // Transform the nsIArray of nsISupportsString's into a JS Array of + // JS strings. + return Array.from( + uri.enumerate(Ci.nsISupportsString), + supportStr => supportStr.data + ); + } else if (uri instanceof Ci.nsISupportsString) { + return uri.data; + } + return uri; + }
- if (uri instanceof Ci.nsIArray) { - // Transform the nsIArray of nsISupportsString's into a JS Array of - // JS strings. - return Array.from( - uri.enumerate(Ci.nsISupportsString), - supportStr => supportStr.data - ); - } else if (uri instanceof Ci.nsISupportsString) { - return uri.data; + // The URI appears to be the the homepage. We want to load it only if + // session restore isn't about to override the homepage. + let willOverride = SessionStartup.willOverrideHomepage; + if (typeof willOverride == "boolean") { + return willOverride ? null : uri; } - return uri; - } + return willOverride.then(willOverrideHomepage => + willOverrideHomepage ? null : uri + ); + })(uri); + + // if using TorConnect, convert these uris to redirects + if (TorConnect.shouldShowTorConnect) { + return Promise.resolve(uri).then((uri) => { + if (uri == null) { + uri = []; + }
- // The URI appears to be the the homepage. We want to load it only if - // session restore isn't about to override the homepage. - let willOverride = SessionStartup.willOverrideHomepage; - if (typeof willOverride == "boolean") { - return willOverride ? null : uri; + uri = TorConnect.getURIsToLoad(uri); + return uri; + }); } - return willOverride.then(willOverrideHomepage => - willOverrideHomepage ? null : uri - ); + return uri; })()); },
@@ -2473,6 +2493,8 @@ var gBrowserInit = {
DownloadsButton.uninit();
+ TorBootstrapUrlbar.uninit(); + gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) { diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 8efb544918b8e..f163073657286 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -10,6 +10,7 @@ override rules using selectors with the same specificity. This applies to both "content" and "skin" packages, which bug 1385444 will unify later. --> <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://branding/content/tor-styles.css" type="text/css"?>
<!-- While these stylesheets are defined in Toolkit, they are only used in the main browser window, so we can load them here. Bug 1474241 is on file to @@ -110,6 +111,7 @@ Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this); Services.scriptloader.loadSubScript("chrome://torbutton/content/tor-circuit-display.js", this); Services.scriptloader.loadSubScript("chrome://torbutton/content/torbutton.js", this); + Services.scriptloader.loadSubScript("chrome://browser/content/torconnect/torBootstrapUrlbar.js", this);
window.onload = gBrowserInit.onLoad.bind(gBrowserInit); window.onunload = gBrowserInit.onUnload.bind(gBrowserInit); diff --git a/browser/base/content/certerror/aboutNetError.js b/browser/base/content/certerror/aboutNetError.js index 31c4838a053da..edf97c2a5daf3 100644 --- a/browser/base/content/certerror/aboutNetError.js +++ b/browser/base/content/certerror/aboutNetError.js @@ -239,7 +239,7 @@ function setErrorPageStrings(err) { document.l10n.setAttributes(titleElement, title); }
-function initPage() { +async function initPage() { // We show an offline support page in case of a system-wide error, // when a user cannot connect to the internet and access the SUMO website. // For example, clock error, which causes certerrors across the web or @@ -258,6 +258,16 @@ function initPage() { }
var err = getErrorCode(); + + // proxyConnectFailure because no-tor running daemon would return this error + if ( + (err === "proxyConnectFailure") && + (await RPMSendQuery("ShouldShowTorConnect")) + ) { + // pass orginal destination as redirect param + const encodedRedirect = encodeURIComponent(document.location.href); + document.location.replace(`about:torconnect?redirect=${encodedRedirect}`); + } // List of error pages with an illustration. let illustratedErrors = [ "malformedURI", diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 02636a6b46b5c..e7f63116ff39e 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -330,6 +330,7 @@ data-l10n-id="urlbar-go-button"/> <hbox id="page-action-buttons" context="pageActionContextMenu"> <toolbartabstop/> +#include ../../components/torconnect/content/torconnect-urlbar.inc.xhtml <hbox id="contextual-feature-recommendation" role="button" hidden="true"> <hbox id="cfr-label-container"> <label id="cfr-label"/> diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index 3b14beaa5b1e3..a95717544b80f 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -21,6 +21,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", ShellService: "resource:///modules/ShellService.jsm", + TorConnect: "resource:///modules/TorConnect.jsm", });
XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () => @@ -258,6 +259,19 @@ function openUILinkIn( aPostData, aReferrerInfo ) { + // make sure users are not faced with the scary red 'tor isn't working' screen + // if they navigate to about:tor before bootstrapped + // + // fixes tor-browser#40752 + // new tabs also redirect to about:tor if browser.newtabpage.enabled is true + // otherwise they go to about:blank + if (TorConnect.shouldShowTorConnect) { + if (url === "about:tor" || + (url === "about:newtab" && Services.prefs.getBoolPref("browser.newtabpage.enabled", false))) { + url = TorConnect.getRedirectURL(url); + } + } + var params;
if (arguments.length == 3 && typeof arguments[2] == "object") { diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 9dfad0358ed75..dc956bc796166 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -703,6 +703,20 @@ let JSWINDOWACTORS = { allFrames: true, },
+ TorConnect: { + parent: { + moduleURI: "resource:///modules/TorConnectParent.jsm", + }, + child: { + moduleURI: "resource:///modules/TorConnectChild.jsm", + events: { + DOMWindowCreated: {}, + }, + }, + + matches: ["about:torconnect","about:torconnect?*"], + }, + Translation: { parent: { moduleURI: "resource:///modules/translation/TranslationParent.jsm", diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 6d283fe67b206..21f673f601d26 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -122,6 +122,10 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::HIDE_FROM_ABOUTABOUT}, {"restartrequired", "chrome://browser/content/aboutRestartRequired.xhtml", nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"torconnect", "chrome://browser/content/torconnect/aboutTorConnect.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 8ce22e9cff519..733abef1a80f6 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -26,6 +26,7 @@ pages = [ 'robots', 'sessionrestore', 'tabcrashed', + 'torconnect', 'welcome', 'welcomeback', ] diff --git a/browser/components/moz.build b/browser/components/moz.build index 66de87290bd83..d15ff30515936 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -53,6 +53,7 @@ DIRS += [ "syncedtabs", "uitour", "urlbar", + "torconnect", "torpreferences", "translation", ] diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 2150c424d8b83..ddeb923784329 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -186,6 +186,10 @@ ChromeUtils.defineModuleGetter( "resource://gre/modules/sessionstore/SessionHistory.jsm" );
+const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + XPCOMUtils.defineLazyServiceGetters(this, { gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"], }); diff --git a/browser/components/torconnect/TorConnectChild.jsm b/browser/components/torconnect/TorConnectChild.jsm new file mode 100644 index 0000000000000..bd6dd549f156d --- /dev/null +++ b/browser/components/torconnect/TorConnectChild.jsm @@ -0,0 +1,9 @@ +// Copyright (c) 2021, The Tor Project, Inc. + +var EXPORTED_SYMBOLS = ["TorConnectChild"]; + +const { RemotePageChild } = ChromeUtils.import( + "resource://gre/actors/RemotePageChild.jsm" +); + +class TorConnectChild extends RemotePageChild {} diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm new file mode 100644 index 0000000000000..dd3d1b2410f95 --- /dev/null +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -0,0 +1,176 @@ +// Copyright (c) 2021, The Tor Project, Inc. + +var EXPORTED_SYMBOLS = ["TorConnectParent"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); +const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } = ChromeUtils.import( + "resource:///modules/TorConnect.jsm" +); +const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +/* +This object is basically a marshalling interface between the TorConnect module +and a particular about:torconnect page +*/ + +class TorConnectParent extends JSWindowActorParent { + constructor(...args) { + super(...args); + + const self = this; + + this.state = { + State: TorConnect.state, + DetectedCensorshiplevel: TorConnect.detectedCensorshiplevel, + StateChanged: false, + ErrorMessage: TorConnect.errorMessage, + ErrorDetails: TorConnect.errorDetails, + BootstrapProgress: TorConnect.bootstrapProgress, + BootstrapStatus: TorConnect.bootstrapStatus, + ShowViewLog: TorConnect.logHasWarningOrError, + QuickStartEnabled: TorSettings.quickstart.enabled, + CountryCodes: TorConnect.countryCodes, + }; + + // JSWindowActiveParent derived objects cannot observe directly, so create a member + // object to do our observing for us + // + // This object converts the various lifecycle events from the TorConnect module, and + // maintains a state object which we pass down to our about:torconnect page, which uses + // the state object to update its UI + this.torConnectObserver = { + observe(aSubject, aTopic, aData) { + let obj = aSubject?.wrappedJSObject; + + // update our state struct based on received torconnect topics and forward on + // to aboutTorConnect.js + self.state.StateChanged = false; + switch (aTopic) { + case TorConnectTopics.StateChange: { + self.state.State = obj.state; + self.state.StateChanged = true; + + // clear any previous error information if we are bootstrapping + if (self.state.State === TorConnectState.Bootstrapping) { + self.state.ErrorMessage = null; + self.state.ErrorDetails = null; + } + break; + } + case TorConnectTopics.BootstrapProgress: { + self.state.BootstrapProgress = obj.progress; + self.state.BootstrapStatus = obj.status; + self.state.ShowViewLog = obj.hasWarnings; + break; + } + case TorConnectTopics.BootstrapComplete: { + // noop + break; + } + case TorConnectTopics.BootstrapError: { + self.state.ErrorMessage = obj.message; + self.state.ErrorDetails = obj.details; + self.state.DetectedCensorshiplevel = obj.censorshipLevel; + + // With severe censorshp, we offer user list of countries to try + if (self.state.DetectedCensorshiplevel == TorCensorshipLevel.Severe) { + self.state.CountryCodes = TorConnect.countryCodes; + } + + self.state.ShowViewLog = true; + break; + } + case TorConnectTopics.FatalError: { + // TODO: handle + break; + } + case TorSettingsTopics.SettingChanged: { + if (aData === TorSettingsData.QuickStartEnabled) { + self.state.QuickStartEnabled = obj.value; + } else { + // this isn't a setting torconnect cares about + return; + } + break; + } + default: { + console.log(`TorConnect: unhandled observe topic '${aTopic}'`); + } + } + + self.sendAsyncMessage("torconnect:state-change", self.state); + }, + }; + + // observe all of the torconnect:.* topics + for (const key in TorConnectTopics) { + const topic = TorConnectTopics[key]; + Services.obs.addObserver(this.torConnectObserver, topic); + } + Services.obs.addObserver( + this.torConnectObserver, + TorSettingsTopics.SettingChanged + ); + } + + willDestroy() { + // stop observing all of our torconnect:.* topics + for (const key in TorConnectTopics) { + const topic = TorConnectTopics[key]; + Services.obs.removeObserver(this.torConnectObserver, topic); + } + Services.obs.removeObserver( + this.torConnectObserver, + TorSettingsTopics.SettingChanged + ); + } + + async receiveMessage(message) { + switch (message.name) { + case "torconnect:set-quickstart": + TorSettings.quickstart.enabled = message.data; + TorSettings.saveToPrefs().applySettings(); + break; + case "torconnect:open-tor-preferences": + TorConnect.openTorPreferences(); + break; + case "torconnect:view-tor-logs": + TorConnect.viewTorLogs(); + break; + case "torconnect:cancel-bootstrap": + TorConnect.cancelBootstrap(); + break; + case "torconnect:begin-bootstrap": + TorConnect.beginBootstrap(); + break; + case "torconnect:begin-autobootstrap": + TorConnect.beginAutoBootstrap(message.data); + break; + case "torconnect:restart": + Services.startup.quit( + Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit + ); + break; + case "torconnect:get-init-args": + // called on AboutTorConnect.init(), pass down all state data it needs to init + + // pretend this is a state transition on init + // so we always get fresh UI + this.state.StateChanged = true; + return { + TorStrings, + TorConnectState, + TorCensorshipLevel, + Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr", + State: this.state, + CountryNames: TorConnect.countryNames, + }; + case "torconnect:get-country-codes": + return TorConnect.getCountryCodes(); + } + return undefined; + } +} diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css new file mode 100644 index 0000000000000..0a3dc9fbd75fd --- /dev/null +++ b/browser/components/torconnect/content/aboutTorConnect.css @@ -0,0 +1,247 @@ + +/* Copyright (c) 2021, The Tor Project, Inc. */ + +@import url("chrome://browser/skin/error-pages.css"); +@import url("chrome://branding/content/tor-styles.css"); + +:root { + --onion-opacity: 1; + --onion-color: var(--card-outline-color); + --onion-radius: 75px; +} + +/* override firefox's default blue focus coloring */ +:focus { + outline: none!important; + box-shadow: 0 0 0 3px var(--purple-30) !important; + border: 1px var(--purple-80) solid !important; +} + +@media (prefers-color-scheme: dark) +{ + :focus { + box-shadow: 0 0 0 3px var(--purple-50)!important; + } +} + +#breadcrumbs { + display: flex; + margin: 0 0 24px 0; + color: var(--grey-40); +} + +#breadcrumbs.hidden { + visibility: hidden; +} + +.breadcrumb-item, .breadcrumb-separator { + display: flex; + margin: 0; + margin-inline-start: 20px; +} + +.breadcrumb-item { + cursor: pointer; + color: var(--in-content-text-color); +} + +.breadcrumb-item:hover { + color: var(--blue-60); +} + +.breadcrumb-separator { + width: 15px; + list-style-image: url("chrome://browser/content/torconnect/arrow-right.svg"); +} + +.breadcrumb-separator:dir(rtl) { + scale: -1 1; +} + +.breadcrumb-icon { + display: inline list-item; + list-style-position: inside; + fill: currentColor; + -moz-context-properties: fill; +} + +.breadcrumb-label { + margin-top: -1px; +} + +#breadcrumbs .active { + color: var(--blue-60); +} + +#breadcrumbs .disabled, #breadcrumbs .disabled:hover { + color: var(--green-90-a40); + cursor: default; +} + +#breadcrumbs .error { + color: var(--red-60); +} + +#connection-assist { + margin-left: 0; +} + +#connection-assist-icon { + list-style-image: url("chrome://browser/content/torconnect/onion-slash-fillable.svg"); +} + +#location-settings-icon { + list-style-image: url("chrome://browser/content/torconnect/globe.svg"); +} + +#try-bridge { + cursor: default; +} + +#try-bridge-icon { + list-style-image: url("chrome://browser/content/torconnect/bridge.svg"); +} + +button.primary { + background-color: var(--purple-60)!important; + color: white!important; + fill: white!important; +} + +button.primary:hover { + background-color: var(--purple-70)!important; + color: white!important; + fill: white!important; +} + +button.primary:active { + background-color: var(--purple-80)!important; + color: white!important; + fill: white!important; +} + +div#locationDropdownLabel { + margin-block: auto; + margin-inline: 4px; +} + +/* these two follow similar css in error-pages.css for buttons */ +@media only screen and (min-width: 480px) { + form#locationDropdown { + margin-inline: 4px; + /* subtracting out the margin is needeed because by + default forms have different margins than buttons */ + max-width: calc(100% - 8px); + } +} + +@media only screen and (max-width: 480px) { + form#locationDropdown, + div#locationDropdownLabel { + margin: 0.66em 0 0; + } + + form#locationDropdown { + width: 100%; + } +} + +form#locationDropdown select { + max-width: 100%; + padding-block: 0; + margin-inline: 0; + font-weight: 700; +} + +/* checkbox css */ +input[type="checkbox"]:not(:disabled) { + background-color: var(--grey-20)!important; +} + +input[type="checkbox"]:not(:disabled):checked { + background-color: var(--purple-60)!important; + color: white; + fill: white; +} + +input[type="checkbox"]:not(:disabled):hover { + /* override firefox's default blue border on hover */ + border-color: var(--purple-70); + background-color: var(--grey-30)!important; +} + +input[type="checkbox"]:not(:disabled):hover:checked { + background-color: var(--purple-70)!important; +} + +input[type="checkbox"]:not(:disabled):active { + background-color: var(--grey-40)!important; +} + +input[type="checkbox"]:not(:disabled):active:checked { + background-color: var(--purple-80)!important; +} + +#progressBackground { + position:fixed; + padding:0; + margin:0; + top:0; + left:0; + width: 0%; + height: 7px; + background-image: linear-gradient(90deg, rgb(20, 218, 221) 0%, rgb(128, 109, 236) 100%); + border-radius: 0; +} + +#connectPageContainer { + margin-top: 10vh; + width: 50%; +} + +#quickstartCheckbox, #quickstartCheckboxLabel { + vertical-align: middle; +} + +/* mirrors p element spacing */ +#viewLogContainer { + margin: 1em 0; + height: 1.2em; + min-height: 1.2em; +} + +#viewLogLink { + position: relative; + display: inline-block; + color: var(--in-content-link-color); +} + +/* hidden apparently only works if no display is set; who knew? */ +#viewLogLink[hidden="true"] { + display: none; +} + +#viewLogLink:hover { + cursor:pointer; +} + +body { + padding: 0px !important; + justify-content: space-between; + background-color: var(--in-content-page-background); +} + +.title { + background-image: url("chrome://browser/content/torconnect/onion.svg"); + -moz-context-properties: fill, fill-opacity; + fill-opacity: var(--onion-opacity); + fill: var(--onion-color); +} + +.title.error { + background-image: url("chrome://browser/content/torconnect/connection-failure.svg"); +} + +.title.location { + background-image: url("chrome://browser/content/torconnect/connection-location.svg"); +} diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js new file mode 100644 index 0000000000000..8710eef95a49e --- /dev/null +++ b/browser/components/torconnect/content/aboutTorConnect.js @@ -0,0 +1,680 @@ +// Copyright (c) 2021, The Tor Project, Inc. + +/* eslint-env mozilla/frame-script */ + +// populated in AboutTorConnect.init() +let TorStrings = {}; +let TorConnectState = {}; +let TorCensorshipLevel = {}; + +const BreadcrumbStatus = Object.freeze({ + Disabled: -1, + Default: 0, + Active: 1, + Error: 2, +}); + +class AboutTorConnect { + selectors = Object.freeze({ + textContainer: { + title: "div.title", + titleText: "h1.title-text", + longContentText: "#connectLongContentText", + }, + progress: { + description: "p#connectShortDescText", + meter: "div#progressBackground", + }, + breadcrumbs: { + container: "#breadcrumbs", + connectionAssist: { + link: "#connection-assist", + label: "#connection-assist .breadcrumb-label", + }, + locationSettings: { + link: "#location-settings", + label: "#location-settings .breadcrumb-label", + }, + tryBridge: { + link: "#try-bridge", + label: "#try-bridge .breadcrumb-label", + }, + }, + viewLog: { + link: "span#viewLogLink", + }, + quickstart: { + container: "div#quickstartContainer", + checkbox: "input#quickstartCheckbox", + label: "label#quickstartCheckboxLabel", + }, + buttons: { + restart: "button#restartButton", + configure: "button#configureButton", + cancel: "button#cancelButton", + connect: "button#connectButton", + tryBridge: "button#tryBridgeButton", + locationDropdownLabel: "div#locationDropdownLabel", + locationDropdown: "form#locationDropdown", + locationDropdownSelect: "form#locationDropdown select", + tryAgain: "button#tryAgainButton", + }, + }); + + elements = Object.freeze({ + title: document.querySelector(this.selectors.textContainer.title), + titleText: document.querySelector(this.selectors.textContainer.titleText), + longContentText: document.querySelector( + this.selectors.textContainer.longContentText + ), + progressDescription: document.querySelector( + this.selectors.progress.description + ), + progressMeter: document.querySelector(this.selectors.progress.meter), + breadcrumbContainer: document.querySelector( + this.selectors.breadcrumbs.container + ), + connectionAssistLink: document.querySelector( + this.selectors.breadcrumbs.connectionAssist.link + ), + connectionAssistLabel: document.querySelector( + this.selectors.breadcrumbs.connectionAssist.label + ), + locationSettingsLink: document.querySelector( + this.selectors.breadcrumbs.locationSettings.link + ), + locationSettingsLabel: document.querySelector( + this.selectors.breadcrumbs.locationSettings.label + ), + tryBridgeLink: document.querySelector( + this.selectors.breadcrumbs.tryBridge.link + ), + tryBridgeLabel: document.querySelector( + this.selectors.breadcrumbs.tryBridge.label + ), + viewLogLink: document.querySelector(this.selectors.viewLog.link), + quickstartContainer: document.querySelector( + this.selectors.quickstart.container + ), + quickstartCheckbox: document.querySelector( + this.selectors.quickstart.checkbox + ), + quickstartLabel: document.querySelector(this.selectors.quickstart.label), + restartButton: document.querySelector(this.selectors.buttons.restart), + configureButton: document.querySelector(this.selectors.buttons.configure), + cancelButton: document.querySelector(this.selectors.buttons.cancel), + connectButton: document.querySelector(this.selectors.buttons.connect), + tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge), + locationDropdownLabel: document.querySelector( + this.selectors.buttons.locationDropdownLabel + ), + locationDropdown: document.querySelector( + this.selectors.buttons.locationDropdown + ), + locationDropdownSelect: document.querySelector( + this.selectors.buttons.locationDropdownSelect + ), + tryAgainButton: document.querySelector(this.selectors.buttons.tryAgain), + }); + + // a redirect url can be passed as a query parameter for the page to + // forward us to once bootstrap completes (otherwise the window will just close) + redirect = null; + + locations = {}; + + beginBootstrap() { + this.hide(this.elements.connectButton); + this.hide(this.elements.quickstartContainer); + this.show(this.elements.cancelButton); + this.elements.cancelButton.focus(); + RPMSendAsyncMessage("torconnect:begin-bootstrap"); + } + + beginAutoBootstrap(countryCode) { + this.hide(this.elements.tryBridgeButton); + this.show(this.elements.cancelButton); + this.elements.cancelButton.focus(); + RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode); + } + + cancelBootstrap() { + RPMSendAsyncMessage("torconnect:cancel-bootstrap"); + } + + /* + Element helper methods + */ + + show(element, primary) { + if (primary) { + element.classList.add("primary"); + } else { + element.classList.remove("primary"); + } + element.removeAttribute("hidden"); + } + + hide(element) { + element.setAttribute("hidden", "true"); + } + + hideButtons() { + this.hide(this.elements.restartButton); + this.hide(this.elements.configureButton); + this.hide(this.elements.cancelButton); + this.hide(this.elements.connectButton); + this.hide(this.elements.tryBridgeButton); + this.hide(this.elements.locationDropdownLabel); + this.hide(this.elements.locationDropdown); + this.hide(this.elements.tryAgainButton); + } + + populateLocations() { + const selectCountryRegion = document.createElement("option"); + selectCountryRegion.textContent = TorStrings.torConnect.selectCountryRegion; + selectCountryRegion.value = ""; + + // get all codes and names from TorStrings + const locationNodes = []; + for (const [code, name] of Object.entries(this.locations)) { + let option = document.createElement("option"); + option.value = code; + option.textContent = name; + locationNodes.push(option); + } + // locale sort by name + locationNodes.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + + this.elements.locationDropdownSelect.append( + selectCountryRegion, + ...locationNodes + ); + } + + populateSpecialLocations(specialLocations) { + this.removeSpecialLocations(); + if (!specialLocations || !specialLocations.length) { + return; + } + + const locationNodes = []; + for (const code of specialLocations) { + const option = document.createElement("option"); + option.value = code; + + // codes (partially) come from rdsys service, so make sure we have a + // string defined for it + let name = this.locations[code]; + if (!name) { + name = code; + } + + option.textContent = name; + locationNodes.push(option); + } + // locale sort by name + locationNodes.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + + const disabledDividerNode = document.createElement("option"); + disabledDividerNode.setAttribute("disabled", true); + disabledDividerNode.className = "divider"; + this.elements.locationDropdownSelect.options[0].after( + ...locationNodes, + disabledDividerNode + ); + } + + removeSpecialLocations() { + const select = this.elements.locationDropdownSelect; + if (select.querySelector(".divider") === null) { + return; + } + + while (select.options.length > 1) { + // Skip the "select country/region" option + const opt = select.options[1]; + opt.remove(); + if (opt.className === "divider") { + break; + } + } + } + + validateLocation() { + const selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + const selectedOption = this.elements.locationDropdownSelect.options[ + selectedIndex + ]; + if (!selectedOption.value) { + this.elements.tryAgainButton.setAttribute("disabled", "disabled"); + } else { + this.elements.tryAgainButton.removeAttribute("disabled"); + } + } + + setTitle(title, className) { + this.elements.titleText.textContent = title; + if (className !== "error") { + this.elements.title.classList.remove("error"); + } + if (className !== "location") { + this.elements.title.classList.remove("location"); + } + if (className) { + this.elements.title.classList.add(className); + } + document.title = title; + } + + setLongText(...args) { + this.elements.longContentText.textContent = ""; + this.elements.longContentText.append(...args); + } + + setProgress(description, visible, percent) { + this.elements.progressDescription.textContent = description; + if (visible) { + this.show(this.elements.progressMeter); + this.elements.progressMeter.style.width = `${percent}%`; + } else { + this.hide(this.elements.progressMeter); + } + } + + setBreadcrumbsStatus(connectionAssist, locationSettings, tryBridge) { + this.elements.breadcrumbContainer.classList.remove("hidden"); + let elems = [ + [this.elements.connectionAssistLink, connectionAssist], + [this.elements.locationSettingsLink, locationSettings], + [this.elements.tryBridgeLink, tryBridge], + ]; + elems.forEach(([elem, status]) => { + elem.classList.remove("disabled"); + elem.classList.remove("active"); + elem.classList.remove("error"); + switch (status) { + case BreadcrumbStatus.Disabled: + elem.classList.add("disabled"); + break; + case BreadcrumbStatus.Active: + elem.classList.add("active"); + break; + case BreadcrumbStatus.Error: + elem.classList.add("error"); + break; + } + }); + } + + hideBreadcrumbs() { + this.elements.breadcrumbContainer.classList.add("hidden"); + } + + /* + These methods update the UI based on the current TorConnect state + */ + + updateUI(state) { + // calls update_$state() + this[`update_${state.State}`](state); + this.elements.quickstartCheckbox.checked = state.QuickStartEnabled; + } + + /* Per-state updates */ + + update_Initial(state) { + const hasError = false; + const showProgressbar = false; + + this.setTitle(TorStrings.torConnect.torConnect, hasError ? "error" : ""); + this.setProgress( + TorStrings.settings.torPreferencesDescription, + showProgressbar + ); + this.hide(this.elements.quickstartContainer); + this.hide(this.elements.viewLogLink); + this.hideButtons(); + } + + update_Configuring(state) { + const hasError = state.ErrorMessage != null; + const showProgressbar = false; + + this.hide(this.elements.quickstartContainer); + this.hide(this.elements.viewLogLink); + this.hideButtons(); + + if (hasError) { + switch (state.DetectedCensorshiplevel) { + case TorCensorshipLevel.None: + // we shouldn't be able to get here + break; + case TorCensorshipLevel.Moderate: + // bootstrap failed once, offer auto bootstrap + this.showConnectionAssistant(state.ErrorDetails); + if (state.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + break; + case TorCensorshipLevel.Severe: + // autobootstrap failed, verify correct location + this.showLocationSettings(state.CountryCodes, state.ErrorMessage); + if (state.StateChanged) { + this.elements.tryAgainButton.focus(); + } + break; + case TorCensorshipLevel.Extreme: + // finally offer to restart tor-browser or go to configure options + this.showFinalError(state); + break; + } + } else { + this.setTitle(TorStrings.torConnect.torConnect, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", showProgressbar); + this.show(this.elements.quickstartContainer); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + if (state.StateChanged) { + this.elements.connectButton.focus(); + } + this.elements.connectButton.textContent = + TorStrings.torConnect.torConnectButton; + } + } + + update_AutoBootstrapping(state) { + const showProgressbar = true; + + if (state.DetectedCensorshiplevel >= TorCensorshipLevel.Severe) { + this.setTitle(TorStrings.torConnect.tryingBridgeAgain, ""); + } else { + this.setTitle(TorStrings.torConnect.tryingBridge, ""); + } + this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); + this.setProgress( + state.BootstrapStatus, + showProgressbar, + state.BootstrapProgress + ); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Active + ); + if (state.ShowViewLog) { + this.show(this.elements.viewLogLink); + } else { + this.hide(this.elements.viewLogLink); + } + this.hideButtons(); + this.show(this.elements.cancelButton, true); + if (state.StateChanged) { + this.elements.cancelButton.focus(); + } + } + + update_Bootstrapping(state) { + const showProgressbar = true; + + this.setTitle(TorStrings.torConnect.torConnecting, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", showProgressbar, state.BootstrapProgress); + this.hideBreadcrumbs(); + if (state.ShowViewLog) { + this.show(this.elements.viewLogLink); + } else { + this.hide(this.elements.viewLogLink); + } + this.hideButtons(); + this.show(this.elements.cancelButton, true); + if (state.StateChanged) { + this.elements.cancelButton.focus(); + } + } + + update_Error(state) { + const showProgressbar = false; + + this.setTitle(state.ErrorMessage, "error"); + this.setLongText(""); + this.setProgress(state.ErrorDetails, showProgressbar); + this.hideButtons(); + this.show(this.elements.viewLogLink); + } + + update_Bootstrapped(state) { + const showProgressbar = true; + + this.setTitle(TorStrings.torConnect.torConnected, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", showProgressbar, 100); + this.hideButtons(); + + // redirects page to the requested redirect url, removes about:torconnect + // from the page stack, so users cannot accidentally go 'back' to the + // now unresponsive page + window.location.replace(this.redirect); + } + + update_Disabled(state) { + // TODO: we should probably have some UX here if a user goes to about:torconnect when + // it isn't in use (eg using tor-launcher or system tor) + } + + showConnectionAssistant(error) { + const hasError = !!error; + this.setTitle( + TorStrings.torConnect.couldNotConnect, + hasError ? "error" : "" + ); + this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); + this.setProgress(error, false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Active, + BreadcrumbStatus.Default, + BreadcrumbStatus.Disabled + ); + this.hideButtons(); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton); + this.show(this.elements.tryBridgeButton, true); + } + + showConfigureConnectionLink(text) { + const pieces = text.split("#1"); + const link = document.createElement("a"); + link.textContent = TorStrings.torConnect.configureConnection; + link.setAttribute("href", "#"); + link.addEventListener("click", e => { + e.preventDefault(); + RPMSendAsyncMessage("torconnect:open-tor-preferences"); + }); + this.setLongText(pieces[0], link, pieces[1]); + } + + showLocationSettings(locations, error) { + const hasError = !!error; + if (hasError) { + this.setTitle(TorStrings.torConnect.errorLocation, "location"); + this.setLongText(TorStrings.torConnect.errorLocationDescription); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Error, + BreadcrumbStatus.Disabled + ); + this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; + } else { + this.setTitle(TorStrings.torConnect.addLocation, "location"); + this.showConfigureConnectionLink( + TorStrings.torConnect.addLocationDescription + ); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Active, + BreadcrumbStatus.Disabled + ); + this.elements.tryAgainButton.textContent = + TorStrings.torConnect.tryBridge; + } + this.setProgress(error, false); + this.hideButtons(); + if (!locations || !locations.length) { + RPMSendQuery("torconnect:get-country-codes").then(codes => { + if (codes && codes.length) { + this.populateSpecialLocations(codes); + } + }); + } else { + this.populateSpecialLocations(locations); + } + this.validateLocation(); + this.show(this.elements.locationDropdownLabel); + this.show(this.elements.locationDropdown); + this.show(this.elements.tryAgainButton, true); + } + + showFinalError(state) { + this.setTitle(TorStrings.torConnect.finalError, "error"); + this.setLongText(TorStrings.torConnect.finalErrorDescription); + this.setProgress(state ? state.ErrorDetails : "", false); + this.hideButtons(); + this.show(this.elements.restartButton); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + } + + initElements(direction) { + document.documentElement.setAttribute("dir", direction); + + this.elements.connectionAssistLink.addEventListener("click", event => { + if (!this.elements.connectionAssistLink.classList.contains("disabled")) { + this.showConnectionAssistant(); + } + }); + this.elements.connectionAssistLabel.textContent = + TorStrings.torConnect.breadcrumbAssist; + this.elements.locationSettingsLink.addEventListener("click", event => { + if (!this.elements.connectionAssistLink.classList.contains("disabled")) { + this.showLocationSettings(); + } + }); + this.elements.locationSettingsLabel.textContent = + TorStrings.torConnect.breadcrumbLocation; + this.elements.tryBridgeLabel.textContent = + TorStrings.torConnect.breadcrumbTryBridge; + + this.elements.viewLogLink.textContent = TorStrings.torConnect.viewLog; + this.elements.viewLogLink.addEventListener("click", event => { + RPMSendAsyncMessage("torconnect:view-tor-logs"); + }); + + this.elements.quickstartCheckbox.addEventListener("change", () => { + const quickstart = this.elements.quickstartCheckbox.checked; + RPMSendAsyncMessage("torconnect:set-quickstart", quickstart); + }); + this.elements.quickstartLabel.textContent = + TorStrings.settings.quickstartCheckbox; + + this.elements.restartButton.textContent = + TorStrings.torConnect.restartTorBrowser; + this.elements.restartButton.addEventListener("click", () => { + RPMSendAsyncMessage("torconnect:restart"); + }); + + this.elements.configureButton.textContent = + TorStrings.torConnect.torConfigure; + this.elements.configureButton.addEventListener("click", () => { + RPMSendAsyncMessage("torconnect:open-tor-preferences"); + }); + + this.elements.cancelButton.textContent = TorStrings.torConnect.cancel; + this.elements.cancelButton.addEventListener("click", () => { + this.cancelBootstrap(); + }); + + this.elements.connectButton.textContent = + TorStrings.torConnect.torConnectButton; + this.elements.connectButton.addEventListener("click", () => { + this.beginBootstrap(); + }); + + this.populateLocations(); + this.elements.locationDropdownSelect.addEventListener("change", () => { + this.validateLocation(); + }); + + this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge; + this.elements.tryBridgeButton.addEventListener("click", () => { + this.beginAutoBootstrap(); + }); + + this.elements.locationDropdownLabel.textContent = + TorStrings.torConnect.yourLocation; + + this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; + this.elements.tryAgainButton.setAttribute("disabled", "disabled"); + this.elements.tryAgainButton.addEventListener("click", () => { + let selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + let selectedOption = this.elements.locationDropdownSelect.options[ + selectedIndex + ]; + + this.beginAutoBootstrap(selectedOption.value); + }); + } + + initObservers() { + // TorConnectParent feeds us state blobs to we use to update our UI + RPMAddMessageListener("torconnect:state-change", ({ data }) => { + this.updateUI(data); + }); + } + + initKeyboardShortcuts() { + document.onkeydown = evt => { + // unfortunately it looks like we still haven't standardized keycodes to + // integers, so we must resort to a string compare here :( + // see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for relevant documentation + if (evt.code === "Escape") { + this.cancelBootstrap(); + } + }; + } + + async init() { + // see if a user has a final destination after bootstrapping + let params = new URLSearchParams(new URL(document.location.href).search); + if (params.has("redirect")) { + const encodedRedirect = params.get("redirect"); + this.redirect = decodeURIComponent(encodedRedirect); + } else { + // if the user gets here manually or via the button in the urlbar + // then we will redirect to about:tor + this.redirect = "about:tor"; + } + + let args = await RPMSendQuery("torconnect:get-init-args"); + + // various constants + TorStrings = Object.freeze(args.TorStrings); + TorConnectState = Object.freeze(args.TorConnectState); + TorCensorshipLevel = Object.freeze(args.TorCensorshipLevel); + this.locations = args.CountryNames; + + this.initElements(args.Direction); + this.initObservers(); + this.initKeyboardShortcuts(); + + // populate UI based on current state + this.updateUI(args.State); + } +} + +const aboutTorConnect = new AboutTorConnect(); +aboutTorConnect.init(); diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml new file mode 100644 index 0000000000000..a98af43e2d53f --- /dev/null +++ b/browser/components/torconnect/content/aboutTorConnect.xhtml @@ -0,0 +1,66 @@ +<!-- Copyright (c) 2021, The Tor Project, Inc. --> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" /> + <link rel="stylesheet" href="chrome://browser/skin/onionPattern.css" type="text/css" media="all" /> + <link rel="stylesheet" href="chrome://browser/content/torconnect/aboutTorConnect.css" type="text/css" media="all" /> + </head> + <body> + <div id="progressBackground"></div> + <div id="connectPageContainer" class="container"> + <div id="breadcrumbs" class="hidden"> + <span id="connection-assist" class="breadcrumb-item"> + <span id="connection-assist-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + <span class="breadcrumb-separator breadcrumb-icon" /> + <span id="location-settings" class="breadcrumb-item"> + <span id="location-settings-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + <span class="breadcrumb-separator breadcrumb-icon" /> + <span id="try-bridge" class="breadcrumb-item"> + <span id="try-bridge-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + </div> + <div id="text-container"> + <div class="title"> + <h1 class="title-text"/> + </div> + <div id="connectLongContent"> + <p id="connectLongContentText" /> + </div> + <div id="connectShortDesc"> + <p id="connectShortDescText" /> + </div> + + <div id="viewLogContainer"> + <span id="viewLogLink" hidden="true"></span> + </div> + + <div id="quickstartContainer"> + <input id="quickstartCheckbox" type="checkbox" /> + <label id="quickstartCheckboxLabel" for="quickstartCheckbox"/> + </div> + + <div id="connectButtonContainer" class="button-container"> + <button id="restartButton" hidden="true"></button> + <button id="configureButton" hidden="true"></button> + <button id="cancelButton" hidden="true"></button> + <button id="connectButton" class="primary" hidden="true"></button> + <button id="tryBridgeButton" class="primary" hidden="true"></button> + <div id="locationDropdownLabel"/> + <form id="locationDropdown" hidden="true"> + <select id="countries"> + </select> + </form> + <button id="tryAgainButton" class="primary" hidden="true"></button> + </div> + </div> + </div> +#include ../../../themes/shared/onionPattern.inc.xhtml + </body> + <script src="chrome://browser/content/torconnect/aboutTorConnect.js"/> +</html> diff --git a/browser/components/torconnect/content/arrow-right.svg b/browser/components/torconnect/content/arrow-right.svg new file mode 100644 index 0000000000000..3f6d8ded52bed --- /dev/null +++ b/browser/components/torconnect/content/arrow-right.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M10.9991 8.352L5.53406 13.818C5.41557 13.9303 5.25792 13.9918 5.09472 13.9895C4.93152 13.9872 4.77567 13.9212 4.66039 13.8057C4.54511 13.6902 4.47951 13.5342 4.47758 13.3709C4.47565 13.2077 4.53754 13.0502 4.65006 12.932L9.58506 7.998L4.65106 3.067C4.53868 2.94864 4.47697 2.79106 4.47909 2.62786C4.48121 2.46466 4.54698 2.30874 4.66239 2.19333C4.7778 2.07792 4.93372 2.01215 5.09692 2.01003C5.26012 2.00792 5.41769 2.06962 5.53606 2.182L11.0001 7.647L10.9991 8.352Z" fill="conte [...] +</svg> diff --git a/browser/components/torconnect/content/bridge.svg b/browser/components/torconnect/content/bridge.svg new file mode 100644 index 0000000000000..5ae3f05dfd082 --- /dev/null +++ b/browser/components/torconnect/content/bridge.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M1 9.48528C1 9.48528 3.82843 9.48528 6.65685 6.65685C9.48528 3.82843 9.48528 1 9.48528 1" stroke="context-fill" stroke-width="1.25" stroke-linecap="round"/> + <path d="M6.65686 15.1421C6.65686 15.1421 6.65686 12.3137 9.48529 9.48529C12.3137 6.65686 15.1421 6.65686 15.1421 6.65686" stroke="context-fill" stroke-width="1.25" stroke-linecap="round"/> +</svg> diff --git a/browser/components/torconnect/content/connection-failure.svg b/browser/components/torconnect/content/connection-failure.svg new file mode 100644 index 0000000000000..8f2005e360556 --- /dev/null +++ b/browser/components/torconnect/content/connection-failure.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <path fill="context-fill" d="M 30,1.875 C 14.467,1.875 1.875,14.467 1.875,30 c 0,6.725546 2.3647525,12.894963 6.3027344,17.734375 l -4.7636719,4.763672 c -0.7834743,0.783474 -0.7834743,2.044651 0,2.828125 0.7834743,0.783474 2.0446507,0.783474 2.828125,0 C 21.046044,40.52782 34.415343,27.146014 47.546875,14.023438 v -0.002 l 6.779297,-6.7792965 c 0.783474,-0.7834743 0.783474,-2.0446507 0,-2.828125 -0.783474,-0.7834743 -2.044651,-0.7834743 -2.828125,0 L 47.734375,8.1777344 C 42.894963,4. [...] + <path fill="#d70022" d="m59.5328 52.4973-10.261-18.5715c-.7112-1.2833-1.9917-1.9258-3.2722-1.9258-1.2806 0-2.5611.6425-3.2704 1.9258l-10.261 18.5715c-1.3701 2.4755.4312 5.5027 3.2704 5.5027h20.5238c2.8373 0 4.6387-3.0272 3.2704-5.5027zm-12.3666-.533-.4666.4642h-1.4l-.4667-.4642v-1.3929l.4667-.4643h1.4l.4666.4643zm0-4.992c0 .3078-.1229.603-.3417.8207s-.5155.34-.8249.34-.6062-.1223-.825-.34-.3417-.5129-.3417-.8207v-6.383c0-.3079.1229-.6031.3417-.8208s.5156-.34.825-.34.6061.1223.8249.34.3 [...] +</svg> diff --git a/browser/components/torconnect/content/connection-location.svg b/browser/components/torconnect/content/connection-location.svg new file mode 100644 index 0000000000000..1e5c41ccf99a0 --- /dev/null +++ b/browser/components/torconnect/content/connection-location.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <path fill="context-fill" d="M 30,1.875 C 14.467,1.875 1.875,14.467 1.875,30 c 0,6.725546 2.3647429,12.894963 6.3027344,17.734375 l -4.7636719,4.763672 c -0.7834743,0.783474 -0.7834743,2.044651 0,2.828125 0.7834743,0.783474 2.0446507,0.783474 2.828125,0 C 21.049647,40.524244 34.416498,27.144859 47.546875,14.023438 v -0.002 l 6.779297,-6.7792965 c 0.783474,-0.7834743 0.783474,-2.0446507 0,-2.828125 -0.783474,-0.7834743 -2.044651,-0.7834743 -2.828125,0 L 47.734375,8.1777344 C 42.894963,4 [...] + <path fill="#ffa436" d="m45 30c-3.713 0-7.274 1.475-9.8995 4.1005s-4.1005 6.1865-4.1005 9.8995 1.475 7.274 4.1005 9.8995 6.1865 4.1005 9.8995 4.1005 7.274-1.475 9.8995-4.1005 4.1005-6.1865 4.1005-9.8995-1.475-7.274-4.1005-9.8995-6.1865-4.1005-9.8995-4.1005zm4.5677 3.2667c1.9167.8229 3.5778 2.1443 4.8108 3.8267 1.233 1.6825 1.9928 3.6644 2.2004 5.7399h-4.1608c-.2298-3.4759-1.4862-6.8054-3.6101-9.5666zm-3.8248 0c2.5257 2.5792 4.06 5.967 4.3326 9.5666h-10.151c.2726-3.5996 1.8069-6.9874 4. [...] +</svg> diff --git a/browser/components/torconnect/content/globe.svg b/browser/components/torconnect/content/globe.svg new file mode 100644 index 0000000000000..f4d1f19b43ce8 --- /dev/null +++ b/browser/components/torconnect/content/globe.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M8 0.5C6.01088 0.5 4.10322 1.29018 2.6967 2.6967C1.29018 4.10322 0.5 6.01088 0.5 8C0.5 9.98912 1.29018 11.8968 2.6967 13.3033C4.10322 14.7098 6.01088 15.5 8 15.5C9.98912 15.5 11.8968 14.7098 13.3033 13.3033C14.7098 11.8968 15.5 9.98912 15.5 8C15.5 6.01088 14.7098 4.10322 13.3033 2.6967C11.8968 1.29018 9.98912 0.5 8 0.5ZM10.447 2.25C11.4738 2.69088 12.3637 3.39877 13.0242 4.30006C13.6848 5.20135 14.0918 6.26313 14.203 7.375H11.974C11.8509 5.51288 11.1778 3.72922 10.04 2.25H10 [...] +</svg> diff --git a/browser/components/torconnect/content/onion-slash-fillable.svg b/browser/components/torconnect/content/onion-slash-fillable.svg new file mode 100644 index 0000000000000..18f1c5a5520bd --- /dev/null +++ b/browser/components/torconnect/content/onion-slash-fillable.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="m14.1161 15.6245c-.0821.0001-.1634-.016-.2393-.0474-.0758-.0314-.1447-.0775-.2027-.1356l-12.749984-12.749c-.109266-.11882-.168406-.27526-.165071-.43666.003335-.16139.068886-.31525.182967-.42946.114078-.11421.267868-.17994.429258-.18345.16139-.00352.3179.05544.43685.16457l12.74998 12.75c.1168.1176.1824.2767.1824.4425s-.0656.3249-.1824.4425c-.058.058-.1269.1039-.2028.1352-.0759.0312-.1571.0471-.2392.0468z" fill-opacity="context-fill-opacity" fill="context-fill" /> + <path d="m 8,0.5000002 c -1.61963,0 -3.1197431,0.5137987 -4.3457031,1.3867188 l 0.84375,0.8417968 0.7792969,0.78125 0.8613281,0.8613282 0.8164062,0.8164062 0.9863281,0.984375 h 0.058594 c 1.00965,0 1.828125,0.818485 1.828125,1.828125 0,0.01968 6.2e-4,0.039074 0,0.058594 L 10.8125,9.0449221 C 10.9334,8.7195921 11,8.3674002 11,8.0000002 c 0,-1.65685 -1.34314,-3 -3,-3 v -1.078125 c 2.25231,0 4.078125,1.825845 4.078125,4.078125 0,0.67051 -0.162519,1.3033281 -0.449219,1.8613281 l 0.861328,0 [...] +</svg> diff --git a/browser/components/torconnect/content/onion-slash.svg b/browser/components/torconnect/content/onion-slash.svg new file mode 100644 index 0000000000000..93eb24b039055 --- /dev/null +++ b/browser/components/torconnect/content/onion-slash.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="m14.1161 15.6245c-.0821.0001-.1634-.016-.2393-.0474-.0758-.0314-.1447-.0775-.2027-.1356l-12.749984-12.749c-.109266-.11882-.168406-.27526-.165071-.43666.003335-.16139.068886-.31525.182967-.42946.114078-.11421.267868-.17994.429258-.18345.16139-.00352.3179.05544.43685.16457l12.74998 12.75c.1168.1176.1824.2767.1824.4425s-.0656.3249-.1824.4425c-.058.058-.1269.1039-.2028.1352-.0759.0312-.1571.0471-.2392.0468z" fill-opacity="context-fill-opacity" fill="#ff0039" /> + <path d="m 8,0.5000002 c -1.61963,0 -3.1197431,0.5137987 -4.3457031,1.3867188 l 0.84375,0.8417968 0.7792969,0.78125 0.8613281,0.8613282 0.8164062,0.8164062 0.9863281,0.984375 h 0.058594 c 1.00965,0 1.828125,0.818485 1.828125,1.828125 0,0.01968 6.2e-4,0.039074 0,0.058594 L 10.8125,9.0449221 C 10.9334,8.7195921 11,8.3674002 11,8.0000002 c 0,-1.65685 -1.34314,-3 -3,-3 v -1.078125 c 2.25231,0 4.078125,1.825845 4.078125,4.078125 0,0.67051 -0.162519,1.3033281 -0.449219,1.8613281 l 0.861328,0 [...] +</svg> diff --git a/browser/components/torconnect/content/onion.svg b/browser/components/torconnect/content/onion.svg new file mode 100644 index 0000000000000..7655a800d9eec --- /dev/null +++ b/browser/components/torconnect/content/onion.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="M 8 0.5 C 3.85786 0.5 0.5 3.85786 0.5 8 C 0.5 12.1421 3.85786 15.5 8 15.5 C 12.1421 15.5 15.5 12.1421 15.5 8 C 15.5 3.85786 12.1421 0.5 8 0.5 z M 8 1.671875 C 11.4949 1.671875 14.328125 4.50507 14.328125 8 C 14.328125 11.4949 11.4949 14.328125 8 14.328125 L 8 13.25 C 10.89951 13.25 13.25 10.89951 13.25 8 C 13.25 5.10051 10.89951 2.75 8 2.75 L 8 1.671875 z M 8 3.921875 C 10.25231 3.921875 12.078125 5.74772 12.078125 8 C 12.078125 10.25231 10.25231 12.078125 8 12.078125 L 8 11 C [...] +</svg> diff --git a/browser/components/torconnect/content/torBootstrapUrlbar.js b/browser/components/torconnect/content/torBootstrapUrlbar.js new file mode 100644 index 0000000000000..e6a88490f33d4 --- /dev/null +++ b/browser/components/torconnect/content/torBootstrapUrlbar.js @@ -0,0 +1,93 @@ +// Copyright (c) 2021, The Tor Project, Inc. + +"use strict"; + +const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import( + "resource:///modules/TorConnect.jsm" +); +const { TorStrings } = ChromeUtils.import( + "resource:///modules/TorStrings.jsm" +); + +var TorBootstrapUrlbar = { + selectors: Object.freeze({ + torConnect: { + box: "hbox#torconnect-box", + label: "label#torconnect-label", + }, + }), + + elements: null, + + updateTorConnectBox: function(state) { + switch(state) + { + case TorConnectState.Initial: + case TorConnectState.Configuring: + case TorConnectState.AutoConfiguring: + case TorConnectState.Error: + case TorConnectState.FatalError: { + this.elements.torConnectBox.removeAttribute("hidden"); + this.elements.torConnectLabel.textContent = + TorStrings.torConnect.torNotConnectedConcise; + this.elements.inputContainer.setAttribute("torconnect", "offline"); + break; + } + case TorConnectState.Bootstrapping: { + this.elements.torConnectBox.removeAttribute("hidden"); + this.elements.torConnectLabel.textContent = + TorStrings.torConnect.torConnectingConcise; + this.elements.inputContainer.setAttribute("torconnect", "connecting"); + break; + } + case TorConnectState.Bootstrapped: { + this.elements.torConnectBox.removeAttribute("hidden"); + this.elements.torConnectLabel.textContent = + TorStrings.torConnect.torConnectedConcise; + this.elements.inputContainer.setAttribute("torconnect", "connected"); + // hide torconnect box after 5 seconds + setTimeout(() => { + this.elements.torConnectBox.setAttribute("hidden", "true"); + }, 5000); + break; + } + case TorConnectState.Disabled: { + this.elements.torConnectBox.setAttribute("hidden", "true"); + break; + } + default: + break; + } + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic === TorConnectTopics.StateChange) { + const obj = aSubject?.wrappedJSObject; + this.updateTorConnectBox(obj?.state); + } + }, + + init: function() { + if (TorConnect.shouldShowTorConnect) { + // browser isn't populated until init + this.elements = Object.freeze({ + torConnectBox: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.box), + torConnectLabel: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.label), + inputContainer: gURLBar._inputContainer, + }) + this.elements.torConnectBox.addEventListener("click", () => { + TorConnect.openTorConnect(); + }); + Services.obs.addObserver(this, TorConnectTopics.StateChange); + this.observing = true; + this.updateTorConnectBox(TorConnect.state); + } + }, + + uninit: function() { + if (this.observing) { + Services.obs.removeObserver(this, TorConnectTopics.StateChange); + } + }, +}; + diff --git a/browser/components/torconnect/content/torconnect-urlbar.css b/browser/components/torconnect/content/torconnect-urlbar.css new file mode 100644 index 0000000000000..5aabcffedbd02 --- /dev/null +++ b/browser/components/torconnect/content/torconnect-urlbar.css @@ -0,0 +1,57 @@ +/* + ensure our torconnect button is always visible (same rule as for the bookmark button) +*/ +hbox.urlbar-page-action#torconnect-box { + display: -moz-inline-box!important; + height: 28px; +} + +label#torconnect-label { + line-height: 28px; + margin: 0; + opacity: 0.6; + padding: 0 0.5em; +} + +/* set appropriate sizes for the non-standard ui densities */ +:root[uidensity=compact] hbox.urlbar-page-action#torconnect-box { + height: 24px; +} +:root[uidensity=compact] label#torconnect-label { + line-height: 24px; +} + + +:root[uidensity=touch] hbox.urlbar-page-action#torconnect-box { + height: 30px; +} +:root[uidensity=touch] label#torconnect-label { + line-height: 30px; +} + + +/* hide when hidden attribute is set */ +hbox.urlbar-page-action#torconnect-box[hidden="true"], +/* hide when user is typing in URL bar */ +#urlbar[usertyping] > #urlbar-input-container > #page-action-buttons > #torconnect-box { + display: none!important; +} + +/* hide urlbar's placeholder text when not connectd to tor */ +hbox#urlbar-input-container[torconnect="offline"] input#urlbar-input::placeholder, +hbox#urlbar-input-container[torconnect="connecting"] input#urlbar-input::placeholder { + opacity: 0; +} + +/* hide search suggestions when not connected to tor */ +hbox#urlbar-input-container[torconnect="offline"] + vbox.urlbarView, +hbox#urlbar-input-container[torconnect="connecting"] + vbox.urlbarView { + display: none!important; +} + +/* hide search icon when we are not connected to tor */ +hbox#urlbar-input-container[torconnect="offline"] > #identity-box[pageproxystate="invalid"] > #identity-icon, +hbox#urlbar-input-container[torconnect="connecting"] > #identity-box[pageproxystate="invalid"] > #identity-icon +{ + display: none!important; +} diff --git a/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml b/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml new file mode 100644 index 0000000000000..60e985a726910 --- /dev/null +++ b/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml @@ -0,0 +1,10 @@ +# Copyright (c) 2021, The Tor Project, Inc. + +<hbox id="torconnect-box" + class="urlbar-icon-wrapper urlbar-page-action" + role="status" + hidden="true"> + <hbox id="torconnect-container"> + <label id="torconnect-label"/> + </hbox> +</hbox> \ No newline at end of file diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn new file mode 100644 index 0000000000000..8ca0b0651523e --- /dev/null +++ b/browser/components/torconnect/jar.mn @@ -0,0 +1,13 @@ +browser.jar: + content/browser/torconnect/torBootstrapUrlbar.js (content/torBootstrapUrlbar.js) + content/browser/torconnect/aboutTorConnect.css (content/aboutTorConnect.css) +* content/browser/torconnect/aboutTorConnect.xhtml (content/aboutTorConnect.xhtml) + content/browser/torconnect/aboutTorConnect.js (content/aboutTorConnect.js) + content/browser/torconnect/arrow-right.svg (content/arrow-right.svg) + content/browser/torconnect/bridge.svg (content/bridge.svg) + content/browser/torconnect/globe.svg (content/globe.svg) + content/browser/torconnect/connection-failure.svg (content/connection-failure.svg) + content/browser/torconnect/connection-location.svg (content/connection-location.svg) + content/browser/torconnect/onion.svg (content/onion.svg) + content/browser/torconnect/onion-slash.svg (content/onion-slash.svg) + content/browser/torconnect/onion-slash-fillable.svg (content/onion-slash-fillable.svg) diff --git a/browser/components/torconnect/moz.build b/browser/components/torconnect/moz.build new file mode 100644 index 0000000000000..eb29c31a42439 --- /dev/null +++ b/browser/components/torconnect/moz.build @@ -0,0 +1,6 @@ +JAR_MANIFESTS += ['jar.mn'] + +EXTRA_JS_MODULES += [ + 'TorConnectChild.jsm', + 'TorConnectParent.jsm', +] diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm index b2f76bdee2f4a..db83e09109bf2 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -10,6 +10,34 @@ const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" );
+const { TorConnect } = ChromeUtils.import( + "resource:///modules/TorConnect.jsm" +); + +// in certain scenarios we want user input uris to open in a new tab if they do so from the +// about:torconnect tab +function maybeUpdateOpenLocationForTorConnect(openUILinkWhere, currentURI, destinationURI) { + try { + // only open in new tab if: + if (// user is navigating away from about:torconnect + currentURI === "about:torconnect" && + // we are trying to open in same tab + openUILinkWhere === "current" && + // only if user still has not bootstrapped + TorConnect.shouldShowTorConnect && + // and user is not just navigating to about:torconnect + destinationURI !== "about:torconnect") { + return "tab"; + } + } catch (e) { + // swallow exception and fall through returning original so we don't accidentally break + // anything if an exception is thrown + console.log(e?.message ? e.message : e); + } + + return openUILinkWhere; +}; + XPCOMUtils.defineLazyModuleGetters(this, { AppConstants: "resource://gre/modules/AppConstants.jsm", BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.jsm", @@ -2418,6 +2446,10 @@ class UrlbarInput { this.selectionStart = this.selectionEnd = 0; }
+ openUILinkWhere = maybeUpdateOpenLocationForTorConnect( + openUILinkWhere, + this.window.gBrowser.currentURI.asciiSpec, + url); if (openUILinkWhere != "current") { this.handleRevert(); } diff --git a/browser/modules/TorProcessService.jsm b/browser/modules/TorProcessService.jsm new file mode 100644 index 0000000000000..201e331b28066 --- /dev/null +++ b/browser/modules/TorProcessService.jsm @@ -0,0 +1,12 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["TorProcessService"]; + +var TorProcessService = { + get isBootstrapDone() { + const svc = Cc["@torproject.org/torlauncher-process-service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + return svc.mIsBootstrapDone; + }, +}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index bc543283d887b..a06914ccf8d9e 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -154,6 +154,8 @@ EXTRA_JS_MODULES += [ "TabsList.jsm", "TabUnloader.jsm", "ThemeVariableMap.jsm", + 'TorConnect.jsm', + 'TorProcessService.jsm', "TorProtocolService.jsm", "TorSettings.jsm", "TorStrings.jsm", diff --git a/browser/themes/shared/urlbar-searchbar.inc.css b/browser/themes/shared/urlbar-searchbar.inc.css index 82675dae2041b..f91278ce5ed3b 100644 --- a/browser/themes/shared/urlbar-searchbar.inc.css +++ b/browser/themes/shared/urlbar-searchbar.inc.css @@ -745,3 +745,6 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after { .searchbar-textbox::placeholder { opacity: 0.69; } + +%include ../../components/torconnect/content/torconnect-urlbar.css + diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index d4489a35009cd..5df0e24bec398 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -17124,9 +17124,56 @@ void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
StylePrefersColorScheme Document::PrefersColorScheme( IgnoreRFP aIgnoreRFP) const { + + // tor-browser#27476 + // should this document ignore resist finger-printing settings with regards to + // setting the color scheme + // currently only enabled for about:torconnect but we could expand to other non- + // SystemPrincipal pages if we wish + const auto documentUsesPreferredColorScheme = [](auto const* constDocument) -> bool { + if (auto* document = const_cast<Document*>(constDocument); document != nullptr) { + auto uri = document->GetDocBaseURI(); + + // try and extract out our prepath and filepath portions of the uri to C-strings + nsAutoCString prePathStr, filePathStr; + if(NS_FAILED(uri->GetPrePath(prePathStr)) || + NS_FAILED(uri->GetFilePath(filePathStr))) { + return false; + } + + // stick them in string view for easy comparisons + std::string_view prePath(prePathStr.get(), prePathStr.Length()), + filePath(filePathStr.get(), filePathStr.Length()); + + // these about URIs will have the user's preferred color scheme exposed to them + // we can place other URIs here in the future if we wish + // see nsIURI.idl for URI part definitions + constexpr struct { + std::string_view prePath; + std::string_view filePath; + } allowedURIs[] = { + { "about:", "torconnect" }, + }; + + // check each uri in the allow list against this document's uri + // verify the prepath and the file path match + for(auto const& uri : allowedURIs) { + if (prePath == uri.prePath && + filePath == uri.filePath) { + // positive match means we can apply dark-mode to the page + return true; + } + } + } + + // do not allow if no match or other error + return false; + }; + if (aIgnoreRFP == IgnoreRFP::No && - nsContentUtils::ShouldResistFingerprinting(this)) { - return StylePrefersColorScheme::Light; + nsContentUtils::ShouldResistFingerprinting(this) && + !documentUsesPreferredColorScheme(this)) { + return StylePrefersColorScheme::Light; }
if (auto* bc = GetBrowsingContext()) { diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index 41c93c51cf3b1..aab4a37e78a8f 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -6212,6 +6212,8 @@ void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) { NS_ENSURE_SUCCESS_VOID(rv);
if (!StringBeginsWith(url, u"about:neterror"_ns) && + // we want about:torconnect pages to be able to close themselves after bootstrap + !StringBeginsWith(url, u"about:torconnect"_ns) && !mBrowsingContext->HadOriginalOpener() && !aTrustedCaller && !IsOnlyTopLevelDocumentInSHistory()) { bool allowClose = diff --git a/toolkit/actors/AboutHttpsOnlyErrorParent.jsm b/toolkit/actors/AboutHttpsOnlyErrorParent.jsm index b949cb065a583..407e014ff6574 100644 --- a/toolkit/actors/AboutHttpsOnlyErrorParent.jsm +++ b/toolkit/actors/AboutHttpsOnlyErrorParent.jsm @@ -15,6 +15,8 @@ const { SessionStore } = ChromeUtils.import( "resource:///modules/sessionstore/SessionStore.jsm" );
+const { TorConnect } = ChromeUtils.import("resource:///modules/TorConnect.jsm"); + class AboutHttpsOnlyErrorParent extends JSWindowActorParent { get browser() { return this.browsingContext.top.embedderElement; @@ -28,7 +30,10 @@ class AboutHttpsOnlyErrorParent extends JSWindowActorParent { case "openInsecure": this.openWebsiteInsecure(this.browser, aMessage.data.inFrame); break; + case "ShouldShowTorConnect": + return TorConnect.shouldShowTorConnect; } + return undefined; }
goBackFromErrorPage(aWindow) { diff --git a/toolkit/components/httpsonlyerror/content/errorpage.js b/toolkit/components/httpsonlyerror/content/errorpage.js index 1dc8c827789b4..cdb269346eeb9 100644 --- a/toolkit/components/httpsonlyerror/content/errorpage.js +++ b/toolkit/components/httpsonlyerror/content/errorpage.js @@ -124,8 +124,17 @@ function addAutofocus(selector, position = "afterbegin") {
/* Initialize Page */
-initPage(); -// Dispatch this event so tests can detect that we finished loading the error page. -// We're using the same event name as neterror because BrowserTestUtils.jsm relies on that. -let event = new CustomEvent("AboutNetErrorLoad", { bubbles: true }); -document.dispatchEvent(event); +RPMSendQuery("ShouldShowTorConnect").then(shouldShow => { + if (shouldShow) { + // pass orginal destination as redirect param + const encodedRedirect = encodeURIComponent(document.location.href); + document.location.replace(`about:torconnect?redirect=${encodedRedirect}`); + return; + } + + initPage(); + // Dispatch this event so tests can detect that we finished loading the error page. + // We're using the same event name as neterror because BrowserTestUtils.jsm relies on that. + let event = new CustomEvent("AboutNetErrorLoad", { bubbles: true }); + document.dispatchEvent(event); +}); diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm index 7bde782e54ce8..ba8cd0f3f97d3 100644 --- a/toolkit/components/processsingleton/MainProcessSingleton.jsm +++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm @@ -29,6 +29,11 @@ MainProcessSingleton.prototype = { null );
+ ChromeUtils.import( + "resource:///modules/TorConnect.jsm", + null + ); + Services.ppmm.loadProcessScript( "chrome://global/content/process-content.js", true diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index 50fb4ea8d4179..225429d95b667 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -67,6 +67,7 @@ let RemotePageAccessManager = { RPMAddMessageListener: ["WWWReachable"], RPMTryPingSecureWWWLink: ["*"], RPMOpenSecureWWWLink: ["*"], + RPMSendQuery: ["ShouldShowTorConnect"], }, "about:certificate": { RPMSendQuery: ["getCertificates"], @@ -102,6 +103,7 @@ let RemotePageAccessManager = { RPMAddToHistogram: ["*"], RPMGetInnerMostURI: ["*"], RPMGetHttpResponseHeader: ["*"], + RPMSendQuery: ["ShouldShowTorConnect"], }, "about:plugins": { RPMSendQuery: ["RequestPlugins"], @@ -213,6 +215,25 @@ let RemotePageAccessManager = { RPMAddMessageListener: ["*"], RPMRemoveMessageListener: ["*"], }, + "about:tbupdate": { + RPMSendQuery: ["FetchUpdateData"], + }, + "about:torconnect": { + RPMAddMessageListener: ["torconnect:state-change"], + RPMSendAsyncMessage: [ + "torconnect:open-tor-preferences", + "torconnect:begin-bootstrap", + "torconnect:begin-autobootstrap", + "torconnect:cancel-bootstrap", + "torconnect:set-quickstart", + "torconnect:view-tor-logs", + "torconnect:restart", + ], + RPMSendQuery: [ + "torconnect:get-init-args", + "torconnect:get-country-codes", + ], + }, },
/** diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm index 4d1b1c59eff5c..cd87b21b0ff9c 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -12,6 +12,17 @@ const { AppConstants } = ChromeUtils.import( const { AUSTLMY } = ChromeUtils.import( "resource://gre/modules/UpdateTelemetry.jsm" ); + +const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +function _shouldRegisterBootstrapObserver(errorCode) { + return errorCode == PROXY_SERVER_CONNECTION_REFUSED && + !TorProtocolService.isBootstrapDone() && + TorProtocolService.ownsTorDaemon; +}; + const { Bits, BitsRequest, @@ -228,6 +239,7 @@ const SERVICE_ERRORS = [ // Custom update error codes const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110; const NETWORK_ERROR_OFFLINE = 111; +const PROXY_SERVER_CONNECTION_REFUSED = 2152398920;
// Error codes should be < 1000. Errors above 1000 represent http status codes const HTTP_ERROR_OFFSET = 1000; @@ -2613,6 +2625,9 @@ UpdateService.prototype = { case "network:offline-status-changed": this._offlineStatusChanged(data); break; + case "torconnect:bootstrap-complete": + this._bootstrapComplete(); + break; case "nsPref:changed": if (data == PREF_APP_UPDATE_LOG || data == PREF_APP_UPDATE_LOG_FILE) { gLogEnabled; // Assigning this before it is lazy-loaded is an error. @@ -3063,6 +3078,35 @@ UpdateService.prototype = { this._attemptResume(); },
+ _registerBootstrapObserver: function AUS__registerBootstrapObserver() { + if (this._registeredBootstrapObserver) { + LOG( + "UpdateService:_registerBootstrapObserver - observer already registered" + ); + return; + } + + LOG( + "UpdateService:_registerBootstrapObserver - waiting for tor bootstrap to " + + "be complete, then forcing another check" + ); + + Services.obs.addObserver(this, "torconnect:bootstrap-complete"); + this._registeredBootstrapObserver = true; + }, + + _bootstrapComplete: function AUS__bootstrapComplete() { + Services.obs.removeObserver(this, "torconnect:bootstrap-complete"); + this._registeredBootstrapObserver = false; + + LOG( + "UpdateService:_bootstrapComplete - bootstrapping complete, forcing " + + "another background check" + ); + + this._attemptResume(); + }, + onCheckComplete: function AUS_onCheckComplete(request, updates) { this._selectAndInstallUpdate(updates); }, @@ -3082,6 +3126,11 @@ UpdateService.prototype = { AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE); } return; + } else if (_shouldRegisterBootstrapObserver(update.errorCode)) { + // Register boostrap observer to try again, but only when we own the + // tor process. + this._registerBootstrapObserver(); + return; }
// Send the error code to telemetry @@ -5843,6 +5892,7 @@ Downloader.prototype = { var state = this._patch.state; var shouldShowPrompt = false; var shouldRegisterOnlineObserver = false; + var shouldRegisterBootstrapObserver = false; var shouldRetrySoon = false; var deleteActiveUpdate = false; let migratedToReadyUpdate = false; @@ -5961,7 +6011,18 @@ Downloader.prototype = { ); shouldRegisterOnlineObserver = true; deleteActiveUpdate = false; - + } else if(_shouldRegisterBootstrapObserver(status)) { + // Register a bootstrap observer to try again. + // The bootstrap observer will continue the incremental download by + // calling downloadUpdate on the active update which continues + // downloading the file from where it was. + LOG("Downloader:onStopRequest - not bootstrapped, register bootstrap observer: true"); + AUSTLMY.pingDownloadCode( + this.isCompleteUpdate, + AUSTLMY.DWNLD_RETRY_OFFLINE + ); + shouldRegisterBootstrapObserver = true; + deleteActiveUpdate = false; // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, // NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned // when disconnecting the internet while a download of a MAR is in @@ -6083,7 +6144,7 @@ Downloader.prototype = {
// Only notify listeners about the stopped state if we // aren't handling an internal retry. - if (!shouldRetrySoon && !shouldRegisterOnlineObserver) { + if (!shouldRetrySoon && !shouldRegisterOnlineObserver && !shouldRegisterBootstrapObserver) { this.updateService.forEachDownloadListener(listener => { listener.onStopRequest(request, status); }); @@ -6269,6 +6330,9 @@ Downloader.prototype = { if (shouldRegisterOnlineObserver) { LOG("Downloader:onStopRequest - Registering online observer"); this.updateService._registerOnlineObserver(); + } else if (shouldRegisterBootstrapObserver) { + LOG("Downloader:onStopRequest - Registering bootstrap observer"); + this.updateService._registerBootstrapObserver(); } else if (shouldRetrySoon) { LOG("Downloader:onStopRequest - Retrying soon"); this.updateService._consecutiveSocketErrors++; diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js index 2ff107b553b2a..f8fa83574df70 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js @@ -70,6 +70,10 @@ function getGlobalScriptIncludes(scriptPath) { let match = line.match(globalScriptsRegExp); if (match) { let sourceFile = match[1] + .replace( + "chrome://browser/content/torconnect/", + "browser/components/torconnect/content/" + ) .replace( "chrome://browser/content/search/", "browser/components/search/content/"
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit d3f26be63d2106c48f47dabd51ff6416d97a9e64 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Apr 7 12:23:55 2022 +0200
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
Changes introduced by !275 --- browser/components/torconnect/TorConnectParent.jsm | 53 +- .../torconnect/content/aboutTorConnect.css | 178 ++++-- .../torconnect/content/aboutTorConnect.js | 675 +++++++++++++-------- .../torconnect/content/aboutTorConnect.xhtml | 24 +- browser/components/torconnect/content/globe.svg | 4 - browser/components/torconnect/jar.mn | 1 - toolkit/modules/RemotePageAccessManager.jsm | 7 +- 7 files changed, 596 insertions(+), 346 deletions(-)
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm index dd3d1b2410f95..3c2a56934c145 100644 --- a/browser/components/torconnect/TorConnectParent.jsm +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -4,13 +4,18 @@ var EXPORTED_SYMBOLS = ["TorConnectParent"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); -const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } = ChromeUtils.import( - "resource:///modules/TorConnect.jsm" -); +const { + InternetStatus, + TorConnect, + TorConnectTopics, + TorConnectState, +} = ChromeUtils.import("resource:///modules/TorConnect.jsm"); const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import( "resource:///modules/TorSettings.jsm" );
+const BroadcastTopic = "about-torconnect:broadcast"; + /* This object is basically a marshalling interface between the TorConnect module and a particular about:torconnect page @@ -24,15 +29,16 @@ class TorConnectParent extends JSWindowActorParent {
this.state = { State: TorConnect.state, - DetectedCensorshiplevel: TorConnect.detectedCensorshiplevel, StateChanged: false, + PreviousState: TorConnectState.Initial, ErrorMessage: TorConnect.errorMessage, ErrorDetails: TorConnect.errorDetails, BootstrapProgress: TorConnect.bootstrapProgress, BootstrapStatus: TorConnect.bootstrapStatus, + InternetStatus: TorConnect.internetStatus, ShowViewLog: TorConnect.logHasWarningOrError, QuickStartEnabled: TorSettings.quickstart.enabled, - CountryCodes: TorConnect.countryCodes, + UIState: TorConnect.uiState, };
// JSWindowActiveParent derived objects cannot observe directly, so create a member @@ -50,6 +56,7 @@ class TorConnectParent extends JSWindowActorParent { self.state.StateChanged = false; switch (aTopic) { case TorConnectTopics.StateChange: { + self.state.PreviousState = self.state.State; self.state.State = obj.state; self.state.StateChanged = true;
@@ -73,13 +80,7 @@ class TorConnectParent extends JSWindowActorParent { case TorConnectTopics.BootstrapError: { self.state.ErrorMessage = obj.message; self.state.ErrorDetails = obj.details; - self.state.DetectedCensorshiplevel = obj.censorshipLevel; - - // With severe censorshp, we offer user list of countries to try - if (self.state.DetectedCensorshiplevel == TorCensorshipLevel.Severe) { - self.state.CountryCodes = TorConnect.countryCodes; - } - + self.state.InternetStatus = TorConnect.internetStatus; self.state.ShowViewLog = true; break; } @@ -114,6 +115,17 @@ class TorConnectParent extends JSWindowActorParent { this.torConnectObserver, TorSettingsTopics.SettingChanged ); + + this.userActionObserver = { + observe(aSubject, aTopic, aData) { + let obj = aSubject?.wrappedJSObject; + if (obj) { + obj.connState = self.state; + self.sendAsyncMessage("torconnect:user-action", obj); + } + }, + }; + Services.obs.addObserver(this.userActionObserver, BroadcastTopic); }
willDestroy() { @@ -126,6 +138,7 @@ class TorConnectParent extends JSWindowActorParent { this.torConnectObserver, TorSettingsTopics.SettingChanged ); + Services.obs.removeObserver(this.userActionObserver, BroadcastTopic); }
async receiveMessage(message) { @@ -137,9 +150,6 @@ class TorConnectParent extends JSWindowActorParent { case "torconnect:open-tor-preferences": TorConnect.openTorPreferences(); break; - case "torconnect:view-tor-logs": - TorConnect.viewTorLogs(); - break; case "torconnect:cancel-bootstrap": TorConnect.cancelBootstrap(); break; @@ -149,21 +159,32 @@ class TorConnectParent extends JSWindowActorParent { case "torconnect:begin-autobootstrap": TorConnect.beginAutoBootstrap(message.data); break; + case "torconnect:view-tor-logs": + TorConnect.viewTorLogs(); + break; case "torconnect:restart": Services.startup.quit( Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit ); break; + case "torconnect:set-ui-state": + TorConnect.uiState = message.data; + this.state.UIState = TorConnect.uiState; + break; + case "torconnect:broadcast-user-action": + Services.obs.notifyObservers(message.data, BroadcastTopic); + break; case "torconnect:get-init-args": // called on AboutTorConnect.init(), pass down all state data it needs to init
// pretend this is a state transition on init // so we always get fresh UI this.state.StateChanged = true; + this.state.UIState = TorConnect.uiState; return { TorStrings, TorConnectState, - TorCensorshipLevel, + InternetStatus, Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr", State: this.state, CountryNames: TorConnect.countryNames, diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css index 0a3dc9fbd75fd..ae6a76db9fe75 100644 --- a/browser/components/torconnect/content/aboutTorConnect.css +++ b/browser/components/torconnect/content/aboutTorConnect.css @@ -10,22 +10,22 @@ --onion-radius: 75px; }
-/* override firefox's default blue focus coloring */ -:focus { +input[type="checkbox"]:focus, select:focus { outline: none!important; box-shadow: 0 0 0 3px var(--purple-30) !important; border: 1px var(--purple-80) solid !important; }
-@media (prefers-color-scheme: dark) +@media (-moz-toolbar-prefers-color-scheme: dark) { - :focus { + input[type="checkbox"]:focus, select:focus { box-shadow: 0 0 0 3px var(--purple-50)!important; } }
#breadcrumbs { display: flex; + align-items: center; margin: 0 0 24px 0; color: var(--grey-40); } @@ -38,15 +38,24 @@ display: flex; margin: 0; margin-inline-start: 20px; + padding: 8px; }
.breadcrumb-item { + align-items: center; cursor: pointer; color: var(--in-content-text-color); + border-radius: 4px; }
.breadcrumb-item:hover { - color: var(--blue-60); + color: var(--in-content-accent-color); + background-color: var(--in-content-button-background-hover); +} + +.breadcrumb-item:active { + color: var(--in-content-accent-color-active); + background-color: var(--in-content-button-background-active); }
.breadcrumb-separator { @@ -60,30 +69,44 @@
.breadcrumb-icon { display: inline list-item; + height: 16px; list-style-position: inside; fill: currentColor; -moz-context-properties: fill; }
-.breadcrumb-label { - margin-top: -1px; +.breadcrumb-item.active { + color: var(--in-content-accent-color); }
-#breadcrumbs .active { - color: var(--blue-60); +.breadcrumb-item.disabled, .breadcrumb-item.disabled:hover, .breadcrumb-item.disabled:active { + color: var(--in-content-text-color); + opacity: 0.4; + cursor: default; }
-#breadcrumbs .disabled, #breadcrumbs .disabled:hover { - color: var(--green-90-a40); - cursor: default; +.breadcrumb-item.error { + color: var(--in-content-danger-button-background); +} + +.breadcrumb-item.error:hover { + color: var(--in-content-danger-button-background-hover); +} + +.breadcrumb-item.error:active { + color: var(--in-content-danger-button-background-active); }
-#breadcrumbs .error { - color: var(--red-60); +.breadcrumb-item.hidden, .breadcrumb-separator.hidden { + display: none; }
-#connection-assist { - margin-left: 0; +#connect-to-tor { + margin-inline-start: 0; +} + +#connect-to-tor-icon { + list-style-image: url("chrome://browser/content/torconnect/onion.svg"); }
#connection-assist-icon { @@ -102,30 +125,28 @@ list-style-image: url("chrome://browser/content/torconnect/bridge.svg"); }
-button.primary { - background-color: var(--purple-60)!important; - color: white!important; - fill: white!important; -} - -button.primary:hover { - background-color: var(--purple-70)!important; - color: white!important; - fill: white!important; -} - -button.primary:active { - background-color: var(--purple-80)!important; - color: white!important; - fill: white!important; +button { + --purple-button-text-color: rgb(251,251,254); + --in-content-primary-button-text-color: var(--purple-button-text-color); + --in-content-primary-button-background: var(--purple-60); + --in-content-primary-button-text-color-hover: var(--purple-button-text-color); + --in-content-primary-button-background-hover: var(--purple-70); + --in-content-primary-button-text-color-active: var(--purple-button-text-color); + --in-content-primary-button-background-active: var(--purple-80); + --in-content-focus-outline-color: var(--purple-60); + fill: white; }
-div#locationDropdownLabel { +#locationDropdownLabel { margin-block: auto; margin-inline: 4px; }
-/* these two follow similar css in error-pages.css for buttons */ +#locationDropdownLabel.error { + color: var(--in-content-danger-button-background) +} + +/* this follows similar css in error-pages.css for buttons */ @media only screen and (min-width: 480px) { form#locationDropdown { margin-inline: 4px; @@ -136,14 +157,13 @@ div#locationDropdownLabel { }
@media only screen and (max-width: 480px) { - form#locationDropdown, - div#locationDropdownLabel { - margin: 0.66em 0 0; + #tryAgainButton { + margin-top: 4px; } +}
- form#locationDropdown { - width: 100%; - } +form#locationDropdown { + width: 240px; }
form#locationDropdown select { @@ -182,21 +202,79 @@ input[type="checkbox"]:not(:disabled):active:checked { background-color: var(--purple-80)!important; }
-#progressBackground { - position:fixed; - padding:0; - margin:0; - top:0; - left:0; +:root { + --progressbar-shadow-start: rgba(255, 255, 255, 0.7); + --progressbar-gradient: linear-gradient(90deg, #FC00FF 0%, #00DBDE 50%, #FC00FF 100%); +} + +@media (-moz-toolbar-prefers-color-scheme: dark) { + :root { + --progressbar-shadow-start: rgba(28, 27, 34, 0.7); + } +} + +#progressBar { + position: fixed; + top: 0; + inset-inline-start: 0; width: 0%; + padding: 0; + margin: 0; + animation: progressAnimation 5s ease infinite; +} + +#progressBackground { + height: 66px; + margin-top: -26px; + background-image: + linear-gradient(var(--progressbar-shadow-start), var(--in-content-page-background) 100%), + var(--progressbar-gradient); + background-position: inherit; + filter: blur(5px); + border-end-end-radius: 33px; +} + +#progressSolid { + position: absolute; + top: 0; + width: 100%; height: 7px; - background-image: linear-gradient(90deg, rgb(20, 218, 221) 0%, rgb(128, 109, 236) 100%); - border-radius: 0; + background-image: var(--progressbar-gradient); + background-position: inherit; +} + +#progressBackground, #progressSolid { + background-size: 200% 100%; +} + +@keyframes progressAnimation { + 0% { + background-position: 200%; + } + 50% { + background-position: 100%; + } + 100% { + background-position: 0%; + } +} + +@keyframes progressAnimation { + 0% { + background-position: 200%; + } + 50% { + background-position: 100%; + } + 100% { + background-position: 0%; + } }
#connectPageContainer { margin-top: 10vh; - width: 50%; + width: 100%; + max-width: 45em; }
#quickstartCheckbox, #quickstartCheckboxLabel { @@ -238,7 +316,7 @@ body { fill: var(--onion-color); }
-.title.error { +.title.offline, .title.assist, .title.final { background-image: url("chrome://browser/content/torconnect/connection-failure.svg"); }
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js index 8710eef95a49e..8b5f7216e9ef8 100644 --- a/browser/components/torconnect/content/aboutTorConnect.js +++ b/browser/components/torconnect/content/aboutTorConnect.js @@ -5,13 +5,23 @@ // populated in AboutTorConnect.init() let TorStrings = {}; let TorConnectState = {}; -let TorCensorshipLevel = {}; +let InternetStatus = {}; + +const UIStates = Object.freeze({ + ConnectToTor: "ConnectToTor", + Offline: "Offline", + ConnectionAssist: "ConnectionAssist", + CouldNotLocate: "CouldNotLocate", + LocationConfirm: "LocationConfirm", + FinalError: "FinalError", +});
const BreadcrumbStatus = Object.freeze({ - Disabled: -1, - Default: 0, - Active: 1, - Error: 2, + Hidden: "hidden", + Disabled: "disabled", + Default: "default", + Active: "active", + Error: "error", });
class AboutTorConnect { @@ -23,24 +33,27 @@ class AboutTorConnect { }, progress: { description: "p#connectShortDescText", - meter: "div#progressBackground", + meter: "div#progressBar", }, breadcrumbs: { container: "#breadcrumbs", + connectToTor: { + link: "#connect-to-tor", + label: "#connect-to-tor .breadcrumb-label", + }, connectionAssist: { + separator: "#connection-assist-separator", link: "#connection-assist", label: "#connection-assist .breadcrumb-label", }, - locationSettings: { - link: "#location-settings", - label: "#location-settings .breadcrumb-label", - }, tryBridge: { + separator: "#try-bridge-separator", link: "#try-bridge", label: "#try-bridge .breadcrumb-label", }, }, viewLog: { + container: "#viewLogContainer", link: "span#viewLogLink", }, quickstart: { @@ -54,10 +67,9 @@ class AboutTorConnect { cancel: "button#cancelButton", connect: "button#connectButton", tryBridge: "button#tryBridgeButton", - locationDropdownLabel: "div#locationDropdownLabel", + locationDropdownLabel: "#locationDropdownLabel", locationDropdown: "form#locationDropdown", locationDropdownSelect: "form#locationDropdown select", - tryAgain: "button#tryAgainButton", }, });
@@ -74,17 +86,23 @@ class AboutTorConnect { breadcrumbContainer: document.querySelector( this.selectors.breadcrumbs.container ), + connectToTorLink: document.querySelector( + this.selectors.breadcrumbs.connectToTor.link + ), + connectToTorLabel: document.querySelector( + this.selectors.breadcrumbs.connectToTor.label + ), + connectionAssistSeparator: document.querySelector( + this.selectors.breadcrumbs.connectionAssist.separator + ), connectionAssistLink: document.querySelector( this.selectors.breadcrumbs.connectionAssist.link ), connectionAssistLabel: document.querySelector( this.selectors.breadcrumbs.connectionAssist.label ), - locationSettingsLink: document.querySelector( - this.selectors.breadcrumbs.locationSettings.link - ), - locationSettingsLabel: document.querySelector( - this.selectors.breadcrumbs.locationSettings.label + tryBridgeSeparator: document.querySelector( + this.selectors.breadcrumbs.tryBridge.separator ), tryBridgeLink: document.querySelector( this.selectors.breadcrumbs.tryBridge.link @@ -92,6 +110,7 @@ class AboutTorConnect { tryBridgeLabel: document.querySelector( this.selectors.breadcrumbs.tryBridge.label ), + viewLogContainer: document.querySelector(this.selectors.viewLog.container), viewLogLink: document.querySelector(this.selectors.viewLog.link), quickstartContainer: document.querySelector( this.selectors.quickstart.container @@ -104,7 +123,6 @@ class AboutTorConnect { configureButton: document.querySelector(this.selectors.buttons.configure), cancelButton: document.querySelector(this.selectors.buttons.cancel), connectButton: document.querySelector(this.selectors.buttons.connect), - tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge), locationDropdownLabel: document.querySelector( this.selectors.buttons.locationDropdownLabel ), @@ -114,27 +132,44 @@ class AboutTorConnect { locationDropdownSelect: document.querySelector( this.selectors.buttons.locationDropdownSelect ), - tryAgainButton: document.querySelector(this.selectors.buttons.tryAgain), + tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge), });
// a redirect url can be passed as a query parameter for the page to // forward us to once bootstrap completes (otherwise the window will just close) redirect = null;
+ uiState = { + currentState: UIStates.ConnectToTor, + connectIsTryAgain: false, + allowAutomaticLocation: true, + selectedLocation: "automatic", + bootstrapCause: UIStates.ConnectToTor, + }; + locations = {};
+ constructor() { + this.uiStates = Object.freeze( + Object.fromEntries([ + [UIStates.ConnectToTor, this.showConnectToTor.bind(this)], + [UIStates.Offline, this.showOffline.bind(this)], + [UIStates.ConnectionAssist, this.showConnectionAssistant.bind(this)], + [UIStates.CouldNotLocate, this.showCouldNotLocate.bind(this)], + [UIStates.LocationConfirm, this.showLocationConfirmation.bind(this)], + [UIStates.FinalError, this.showFinalError.bind(this)], + ]) + ); + } + beginBootstrap() { - this.hide(this.elements.connectButton); - this.hide(this.elements.quickstartContainer); - this.show(this.elements.cancelButton); - this.elements.cancelButton.focus(); RPMSendAsyncMessage("torconnect:begin-bootstrap"); }
beginAutoBootstrap(countryCode) { - this.hide(this.elements.tryBridgeButton); - this.show(this.elements.cancelButton); - this.elements.cancelButton.focus(); + if (countryCode === "automatic") { + countryCode = ""; + } RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode); }
@@ -142,16 +177,24 @@ class AboutTorConnect { RPMSendAsyncMessage("torconnect:cancel-bootstrap"); }
+ transitionUIState(nextState, connState) { + if (nextState !== this.uiState.currentState) { + this.uiState.currentState = nextState; + this.saveUIState(); + } + this.uiStates[nextState](connState); + } + + saveUIState() { + RPMSendAsyncMessage("torconnect:set-ui-state", this.uiState); + } + /* Element helper methods */
show(element, primary) { - if (primary) { - element.classList.add("primary"); - } else { - element.classList.remove("primary"); - } + element.classList.toggle("primary", primary !== undefined && primary); element.removeAttribute("hidden"); }
@@ -160,14 +203,14 @@ class AboutTorConnect { }
hideButtons() { + this.hide(this.elements.quickstartContainer); this.hide(this.elements.restartButton); this.hide(this.elements.configureButton); this.hide(this.elements.cancelButton); this.hide(this.elements.connectButton); - this.hide(this.elements.tryBridgeButton); this.hide(this.elements.locationDropdownLabel); this.hide(this.elements.locationDropdown); - this.hide(this.elements.tryAgainButton); + this.hide(this.elements.tryBridgeButton); }
populateLocations() { @@ -187,31 +230,29 @@ class AboutTorConnect { locationNodes.sort((left, right) => left.textContent.localeCompare(right.textContent) ); - this.elements.locationDropdownSelect.append( selectCountryRegion, ...locationNodes ); }
- populateSpecialLocations(specialLocations) { - this.removeSpecialLocations(); - if (!specialLocations || !specialLocations.length) { + populateFrequentLocations(locations) { + this.removeFrequentLocations(); + if (!locations || !locations.length) { return; }
const locationNodes = []; - for (const code of specialLocations) { + for (const code of locations) { const option = document.createElement("option"); option.value = code; - + option.className = "frequent-location"; // codes (partially) come from rdsys service, so make sure we have a // string defined for it let name = this.locations[code]; if (!name) { name = code; } - option.textContent = name; locationNodes.push(option); } @@ -220,28 +261,27 @@ class AboutTorConnect { left.textContent.localeCompare(right.textContent) );
- const disabledDividerNode = document.createElement("option"); - disabledDividerNode.setAttribute("disabled", true); - disabledDividerNode.className = "divider"; + const frequentGroup = document.createElement("optgroup"); + frequentGroup.setAttribute( + "label", + TorStrings.torConnect.frequentLocations + ); + frequentGroup.className = "frequent-location"; + const locationGroup = document.createElement("optgroup"); + locationGroup.setAttribute("label", TorStrings.torConnect.otherLocations); + locationGroup.className = "frequent-location"; + // options[0] is either "Select Country or Region" or "Automatic" this.elements.locationDropdownSelect.options[0].after( + frequentGroup, ...locationNodes, - disabledDividerNode + locationGroup ); }
- removeSpecialLocations() { + removeFrequentLocations() { const select = this.elements.locationDropdownSelect; - if (select.querySelector(".divider") === null) { - return; - } - - while (select.options.length > 1) { - // Skip the "select country/region" option - const opt = select.options[1]; - opt.remove(); - if (opt.className === "divider") { - break; - } + for (const option of select.querySelectorAll(".frequent-location")) { + option.remove(); } }
@@ -251,20 +291,15 @@ class AboutTorConnect { selectedIndex ]; if (!selectedOption.value) { - this.elements.tryAgainButton.setAttribute("disabled", "disabled"); + this.elements.tryBridgeButton.setAttribute("disabled", "disabled"); } else { - this.elements.tryAgainButton.removeAttribute("disabled"); + this.elements.tryBridgeButton.removeAttribute("disabled"); } }
setTitle(title, className) { this.elements.titleText.textContent = title; - if (className !== "error") { - this.elements.title.classList.remove("error"); - } - if (className !== "location") { - this.elements.title.classList.remove("location"); - } + this.elements.title.className = "title"; if (className) { this.elements.title.classList.add(className); } @@ -286,28 +321,30 @@ class AboutTorConnect { } }
- setBreadcrumbsStatus(connectionAssist, locationSettings, tryBridge) { + setBreadcrumbsStatus(connectToTor, connectionAssist, tryBridge) { this.elements.breadcrumbContainer.classList.remove("hidden"); - let elems = [ - [this.elements.connectionAssistLink, connectionAssist], - [this.elements.locationSettingsLink, locationSettings], - [this.elements.tryBridgeLink, tryBridge], + const elems = [ + [this.elements.connectToTorLink, connectToTor, null], + [ + this.elements.connectionAssistLink, + connectionAssist, + this.elements.connectionAssistSeparator, + ], + [ + this.elements.tryBridgeLink, + tryBridge, + this.elements.tryBridgeSeparator, + ], ]; - elems.forEach(([elem, status]) => { - elem.classList.remove("disabled"); - elem.classList.remove("active"); - elem.classList.remove("error"); - switch (status) { - case BreadcrumbStatus.Disabled: - elem.classList.add("disabled"); - break; - case BreadcrumbStatus.Active: - elem.classList.add("active"); - break; - case BreadcrumbStatus.Error: - elem.classList.add("error"); - break; + elems.forEach(([elem, status, separator]) => { + elem.classList.remove(BreadcrumbStatus.Hidden); + elem.classList.remove(BreadcrumbStatus.Disabled); + elem.classList.remove(BreadcrumbStatus.Active); + elem.classList.remove(BreadcrumbStatus.Error); + if (status !== "") { + elem.classList.add(status); } + separator?.classList.toggle("hidden", status === BreadcrumbStatus.Hidden); }); }
@@ -328,124 +365,59 @@ class AboutTorConnect { /* Per-state updates */
update_Initial(state) { - const hasError = false; - const showProgressbar = false; - - this.setTitle(TorStrings.torConnect.torConnect, hasError ? "error" : ""); - this.setProgress( - TorStrings.settings.torPreferencesDescription, - showProgressbar - ); - this.hide(this.elements.quickstartContainer); - this.hide(this.elements.viewLogLink); - this.hideButtons(); + this.showConnectToTor(state); }
update_Configuring(state) { - const hasError = state.ErrorMessage != null; - const showProgressbar = false; - - this.hide(this.elements.quickstartContainer); - this.hide(this.elements.viewLogLink); - this.hideButtons(); - - if (hasError) { - switch (state.DetectedCensorshiplevel) { - case TorCensorshipLevel.None: - // we shouldn't be able to get here - break; - case TorCensorshipLevel.Moderate: - // bootstrap failed once, offer auto bootstrap - this.showConnectionAssistant(state.ErrorDetails); - if (state.StateChanged) { - this.elements.tryBridgeButton.focus(); - } - break; - case TorCensorshipLevel.Severe: - // autobootstrap failed, verify correct location - this.showLocationSettings(state.CountryCodes, state.ErrorMessage); - if (state.StateChanged) { - this.elements.tryAgainButton.focus(); - } - break; - case TorCensorshipLevel.Extreme: - // finally offer to restart tor-browser or go to configure options - this.showFinalError(state); - break; - } - } else { - this.setTitle(TorStrings.torConnect.torConnect, ""); - this.setLongText(TorStrings.settings.torPreferencesDescription); - this.setProgress("", showProgressbar); - this.show(this.elements.quickstartContainer); - this.show(this.elements.configureButton); - this.show(this.elements.connectButton, true); - if (state.StateChanged) { - this.elements.connectButton.focus(); - } - this.elements.connectButton.textContent = - TorStrings.torConnect.torConnectButton; + if ( + state.StateChanged && + (state.PreviousState === TorConnectState.Bootstrapping || + state.PreviousState === TorConnectState.AutoBootstrapping) + ) { + // The bootstrap has been cancelled + this.transitionUIState(this.uiState.bootstrapCause, state); } }
update_AutoBootstrapping(state) { - const showProgressbar = true; - - if (state.DetectedCensorshiplevel >= TorCensorshipLevel.Severe) { - this.setTitle(TorStrings.torConnect.tryingBridgeAgain, ""); - } else { - this.setTitle(TorStrings.torConnect.tryingBridge, ""); - } - this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); - this.setProgress( - state.BootstrapStatus, - showProgressbar, - state.BootstrapProgress - ); - this.setBreadcrumbsStatus( - BreadcrumbStatus.Disabled, - BreadcrumbStatus.Disabled, - BreadcrumbStatus.Active - ); - if (state.ShowViewLog) { - this.show(this.elements.viewLogLink); - } else { - this.hide(this.elements.viewLogLink); - } - this.hideButtons(); - this.show(this.elements.cancelButton, true); - if (state.StateChanged) { - this.elements.cancelButton.focus(); - } + this.showBootstrapping(state); }
update_Bootstrapping(state) { - const showProgressbar = true; - - this.setTitle(TorStrings.torConnect.torConnecting, ""); - this.setLongText(TorStrings.settings.torPreferencesDescription); - this.setProgress("", showProgressbar, state.BootstrapProgress); - this.hideBreadcrumbs(); - if (state.ShowViewLog) { - this.show(this.elements.viewLogLink); - } else { - this.hide(this.elements.viewLogLink); - } - this.hideButtons(); - this.show(this.elements.cancelButton, true); - if (state.StateChanged) { - this.elements.cancelButton.focus(); - } + this.showBootstrapping(state); }
update_Error(state) { - const showProgressbar = false; - - this.setTitle(state.ErrorMessage, "error"); - this.setLongText(""); - this.setProgress(state.ErrorDetails, showProgressbar); - this.hideButtons(); - this.show(this.elements.viewLogLink); + if (!this.uiState.connectIsTryAgain) { + // TorConnect.hasBootstrapEverFailed remains false in case of Internet + // offline + this.uiState.connectIsTryAgain = true; + this.saveUIState(); + } + if (!state.StateChanged) { + return; + } + if (state.InternetStatus === InternetStatus.Offline) { + this.transitionUIState(UIStates.Offline, state); + } else if (state.PreviousState === TorConnectState.Bootstrapping) { + this.transitionUIState(UIStates.ConnectionAssist, state); + } else if (state.PreviousState === TorConnectState.AutoBootstrapping) { + if (this.uiState.bootstrapCause === UIStates.ConnectionAssist) { + this.transitionUIState( + this.getLocation() === "automatic" + ? UIStates.CouldNotLocate + : UIStates.LocationConfirm, + state + ); + } else { + this.transitionUIState(UIStates.FinalError, state); + } + } else { + console.error( + "We received an error starting from an unexpected state", + state + ); + } }
update_Bootstrapped(state) { @@ -467,23 +439,165 @@ class AboutTorConnect { // it isn't in use (eg using tor-launcher or system tor) }
- showConnectionAssistant(error) { - const hasError = !!error; - this.setTitle( - TorStrings.torConnect.couldNotConnect, - hasError ? "error" : "" + showConnectToTor(state) { + this.setTitle(TorStrings.torConnect.torConnect, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", false); + this.hide(this.elements.viewLogContainer); + this.hideButtons(); + this.show(this.elements.quickstartContainer); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + if (state?.StateChanged) { + this.elements.connectButton.focus(); + } + if (this.uiState.connectIsTryAgain) { + this.setBreadcrumbsStatus( + BreadcrumbStatus.Active, + BreadcrumbStatus.Default, + BreadcrumbStatus.Disabled + ); + this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain; + } + this.uiState.bootstrapCause = UIStates.ConnectToTor; + this.saveUIState(); + } + + showBootstrapping(state) { + const showProgressbar = true; + let title = ""; + let description = ""; + const breadcrumbs = [ + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Disabled, + ]; + switch (this.uiState.bootstrapCause) { + case UIStates.ConnectToTor: + breadcrumbs[0] = BreadcrumbStatus.Active; + title = this.uiState.connectIsTryAgain + ? TorStrings.torConnect.tryAgain + : TorStrings.torConnect.torConnecting; + description = TorStrings.settings.torPreferencesDescription; + break; + case UIStates.ConnectionAssist: + breadcrumbs[2] = BreadcrumbStatus.Active; + title = TorStrings.torConnect.tryingBridge; + description = TorStrings.torConnect.assistDescription; + break; + case UIStates.CouldNotLocate: + breadcrumbs[2] = BreadcrumbStatus.Active; + title = TorStrings.torConnect.tryingBridgeAgain; + description = TorStrings.torConnect.errorLocationDescription; + break; + case UIStates.LocationConfirm: + breadcrumbs[2] = BreadcrumbStatus.Active; + title = TorStrings.torConnect.tryingBridgeAgain; + description = TorStrings.torConnect.isLocationCorrectDescription; + break; + } + this.setTitle(title, ""); + this.showConfigureConnectionLink(description); + this.setProgress("", showProgressbar, state.BootstrapProgress); + this.setBreadcrumbsStatus(...breadcrumbs); + this.hideButtons(); + if (state.ShowViewLog) { + this.show(this.elements.viewLogContainer); + } else { + this.hide(this.elements.viewLogContainer); + } + this.show(this.elements.cancelButton, true); + if (state.StateChanged) { + this.elements.cancelButton.focus(); + } + } + + showOffline(error) { + this.setTitle(TorStrings.torConnect.noInternet, "offline"); + this.setLongText(TorStrings.torConnect.noInternetDescription); + this.setProgress(error, false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Active, + BreadcrumbStatus.Hidden ); + this.show(this.elements.viewLogContainer); + this.hideButtons(); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain; + } + + showConnectionAssistant(state) { + this.setTitle(TorStrings.torConnect.couldNotConnect, "assist"); this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); - this.setProgress(error, false); + this.setProgress(state?.ErrorDetails, false); this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, BreadcrumbStatus.Active, + BreadcrumbStatus.Disabled + ); + this.showLocationForm(false, TorStrings.torConnect.tryBridge); + if (state?.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + this.uiState.bootstrapCause = UIStates.ConnectionAssist; + this.saveUIState(); + } + + showCouldNotLocate(state) { + this.uiState.allowAutomaticLocation = false; + this.setTitle(TorStrings.torConnect.errorLocation, "location"); + this.showConfigureConnectionLink( + TorStrings.torConnect.errorLocationDescription + ); + this.setProgress(state.ErrorMessage, false); + this.setBreadcrumbsStatus( BreadcrumbStatus.Default, + BreadcrumbStatus.Active, BreadcrumbStatus.Disabled ); + this.show(this.elements.viewLogContainer); + this.showLocationForm(true, TorStrings.torConnect.tryBridge); + if (state.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + this.uiState.bootstrapCause = UIStates.CouldNotLocate; + this.saveUIState(); + } + + showLocationConfirmation(state) { + this.setTitle(TorStrings.torConnect.isLocationCorrect, "location"); + this.showConfigureConnectionLink( + TorStrings.torConnect.isLocationCorrectDescription + ); + this.setProgress(state.ErrorMessage, false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Default, + BreadcrumbStatus.Active + ); + this.show(this.elements.viewLogContainer); + this.showLocationForm(true, TorStrings.torConnect.tryAgain); + if (state.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + this.uiState.bootstrapCause = UIStates.LocationConfirm; + this.saveUIState(); + } + + showFinalError(state) { + this.setTitle(TorStrings.torConnect.finalError, "final"); + this.setLongText(TorStrings.torConnect.finalErrorDescription); + this.setProgress(state ? state.ErrorDetails : "", false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Default, + BreadcrumbStatus.Active + ); this.hideButtons(); - this.show(this.elements.configureButton); - this.show(this.elements.connectButton); - this.show(this.elements.tryBridgeButton, true); + this.show(this.elements.restartButton); + this.show(this.elements.configureButton, true); }
showConfigureConnectionLink(text) { @@ -495,80 +609,100 @@ class AboutTorConnect { e.preventDefault(); RPMSendAsyncMessage("torconnect:open-tor-preferences"); }); - this.setLongText(pieces[0], link, pieces[1]); - } - - showLocationSettings(locations, error) { - const hasError = !!error; - if (hasError) { - this.setTitle(TorStrings.torConnect.errorLocation, "location"); - this.setLongText(TorStrings.torConnect.errorLocationDescription); - this.setBreadcrumbsStatus( - BreadcrumbStatus.Disabled, - BreadcrumbStatus.Error, - BreadcrumbStatus.Disabled - ); - this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; + if (pieces.length > 1) { + const first = pieces.shift(); + this.setLongText(first, link, ...pieces); } else { - this.setTitle(TorStrings.torConnect.addLocation, "location"); - this.showConfigureConnectionLink( - TorStrings.torConnect.addLocationDescription - ); - this.setBreadcrumbsStatus( - BreadcrumbStatus.Default, - BreadcrumbStatus.Active, - BreadcrumbStatus.Disabled - ); - this.elements.tryAgainButton.textContent = - TorStrings.torConnect.tryBridge; + this.setLongText(text); } - this.setProgress(error, false); + } + + showLocationForm(isError, buttonLabel) { this.hideButtons(); - if (!locations || !locations.length) { - RPMSendQuery("torconnect:get-country-codes").then(codes => { - if (codes && codes.length) { - this.populateSpecialLocations(codes); - } - }); + RPMSendQuery("torconnect:get-country-codes").then(codes => { + if (codes && codes.length) { + this.populateFrequentLocations(codes); + this.setLocationFromState(); + } + }); + let firstOpt = this.elements.locationDropdownSelect.options[0]; + if (this.uiState.allowAutomaticLocation) { + firstOpt.value = "automatic"; + firstOpt.textContent = TorStrings.torConnect.automatic; } else { - this.populateSpecialLocations(locations); + firstOpt.value = ""; + firstOpt.textContent = TorStrings.torConnect.selectCountryRegion; } + this.setLocationFromState(); this.validateLocation(); this.show(this.elements.locationDropdownLabel); this.show(this.elements.locationDropdown); - this.show(this.elements.tryAgainButton, true); + this.elements.locationDropdownLabel.classList.toggle("error", isError); + this.show(this.elements.tryBridgeButton, true); + this.elements.tryBridgeButton.classList.toggle("danger-button", isError); + if (buttonLabel !== undefined) { + this.elements.tryBridgeButton.textContent = buttonLabel; + } }
- showFinalError(state) { - this.setTitle(TorStrings.torConnect.finalError, "error"); - this.setLongText(TorStrings.torConnect.finalErrorDescription); - this.setProgress(state ? state.ErrorDetails : "", false); - this.hideButtons(); - this.show(this.elements.restartButton); - this.show(this.elements.configureButton); - this.show(this.elements.connectButton, true); + getLocation() { + const selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + return this.elements.locationDropdownSelect.options[selectedIndex].value; + } + + setLocationFromState() { + if (this.getLocation() === this.uiState.selectedLocation) { + return; + } + const options = this.elements.locationDropdownSelect.options; + // We need to do this way, because we have repeated values that break + // the .value way to select (which would however require the label, + // rather than the code)... + for (let i = 0; i < options.length; i++) { + if (options[i].value === this.uiState.selectedLocation) { + this.elements.locationDropdownSelect.selectedIndex = i; + break; + } + } + this.validateLocation(); }
initElements(direction) { document.documentElement.setAttribute("dir", direction);
+ this.elements.connectToTorLink.addEventListener("click", event => { + if (this.uiState.currentState === UIStates.ConnectToTor) { + return; + } + this.transitionUIState(UIStates.ConnectToTor, null); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + uiState: UIStates.ConnectToTor, + }); + }); + this.elements.connectToTorLabel.textContent = + TorStrings.torConnect.torConnect; this.elements.connectionAssistLink.addEventListener("click", event => { - if (!this.elements.connectionAssistLink.classList.contains("disabled")) { - this.showConnectionAssistant(); + if ( + this.elements.connectionAssistLink.classList.contains( + BreadcrumbStatus.Active + ) || + this.elements.connectionAssistLink.classList.contains( + BreadcrumbStatus.Disabled + ) + ) { + return; } + this.transitionUIState(UIStates.ConnectionAssist, null); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + uiState: UIStates.ConnectionAssist, + }); }); this.elements.connectionAssistLabel.textContent = TorStrings.torConnect.breadcrumbAssist; - this.elements.locationSettingsLink.addEventListener("click", event => { - if (!this.elements.connectionAssistLink.classList.contains("disabled")) { - this.showLocationSettings(); - } - }); - this.elements.locationSettingsLabel.textContent = - TorStrings.torConnect.breadcrumbLocation; this.elements.tryBridgeLabel.textContent = TorStrings.torConnect.breadcrumbTryBridge;
+ this.hide(this.elements.viewLogContainer); this.elements.viewLogLink.textContent = TorStrings.torConnect.viewLog; this.elements.viewLogLink.addEventListener("click", event => { RPMSendAsyncMessage("torconnect:view-tor-logs"); @@ -606,26 +740,25 @@ class AboutTorConnect {
this.populateLocations(); this.elements.locationDropdownSelect.addEventListener("change", () => { + this.uiState.selectedLocation = this.getLocation(); + this.saveUIState(); this.validateLocation(); - }); - - this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge; - this.elements.tryBridgeButton.addEventListener("click", () => { - this.beginAutoBootstrap(); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + location: this.uiState.selectedLocation, + }); });
this.elements.locationDropdownLabel.textContent = TorStrings.torConnect.yourLocation;
- this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; - this.elements.tryAgainButton.setAttribute("disabled", "disabled"); - this.elements.tryAgainButton.addEventListener("click", () => { - let selectedIndex = this.elements.locationDropdownSelect.selectedIndex; - let selectedOption = this.elements.locationDropdownSelect.options[ - selectedIndex - ]; - - this.beginAutoBootstrap(selectedOption.value); + this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge; + this.elements.tryBridgeButton.addEventListener("click", () => { + const value = this.getLocation(); + if (value === "automatic") { + this.beginAutoBootstrap(); + } else { + this.beginAutoBootstrap(value); + } }); }
@@ -634,6 +767,15 @@ class AboutTorConnect { RPMAddMessageListener("torconnect:state-change", ({ data }) => { this.updateUI(data); }); + RPMAddMessageListener("torconnect:user-action", ({ data }) => { + if (data.location) { + this.uiState.selectedLocation = data.location; + this.setLocationFromState(); + } + if (data.uiState !== undefined) { + this.transitionUIState(data.uiState, data.connState); + } + }); }
initKeyboardShortcuts() { @@ -664,13 +806,20 @@ class AboutTorConnect { // various constants TorStrings = Object.freeze(args.TorStrings); TorConnectState = Object.freeze(args.TorConnectState); - TorCensorshipLevel = Object.freeze(args.TorCensorshipLevel); + InternetStatus = Object.freeze(args.InternetStatus); this.locations = args.CountryNames;
this.initElements(args.Direction); this.initObservers(); this.initKeyboardShortcuts();
+ if (Object.keys(args.State.UIState).length) { + this.uiState = args.State.UIState; + } else { + args.State.UIState = this.uiState; + this.saveUIState(); + } + this.uiStates[this.uiState.currentState](args.State); // populate UI based on current state this.updateUI(args.State); } diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml index a98af43e2d53f..77d2e68895708 100644 --- a/browser/components/torconnect/content/aboutTorConnect.xhtml +++ b/browser/components/torconnect/content/aboutTorConnect.xhtml @@ -7,19 +7,22 @@ <link rel="stylesheet" href="chrome://browser/content/torconnect/aboutTorConnect.css" type="text/css" media="all" /> </head> <body> - <div id="progressBackground"></div> + <div id="progressBar"> + <div id="progressBackground" /> + <div id="progressSolid" /> + </div> <div id="connectPageContainer" class="container"> <div id="breadcrumbs" class="hidden"> - <span id="connection-assist" class="breadcrumb-item"> - <span id="connection-assist-icon" class="breadcrumb-icon" /> + <span id="connect-to-tor" class="breadcrumb-item"> + <span id="connect-to-tor-icon" class="breadcrumb-icon" /> <span class="breadcrumb-label"/> </span> - <span class="breadcrumb-separator breadcrumb-icon" /> - <span id="location-settings" class="breadcrumb-item"> - <span id="location-settings-icon" class="breadcrumb-icon" /> + <span id="connection-assist-separator" class="breadcrumb-separator breadcrumb-icon" /> + <span id="connection-assist" class="breadcrumb-item"> + <span id="connection-assist-icon" class="breadcrumb-icon" /> <span class="breadcrumb-label"/> </span> - <span class="breadcrumb-separator breadcrumb-icon" /> + <span id="try-bridge-separator" class="breadcrumb-separator breadcrumb-icon" /> <span id="try-bridge" class="breadcrumb-item"> <span id="try-bridge-icon" class="breadcrumb-icon" /> <span class="breadcrumb-label"/> @@ -37,7 +40,7 @@ </div>
<div id="viewLogContainer"> - <span id="viewLogLink" hidden="true"></span> + <span id="viewLogLink"></span> </div>
<div id="quickstartContainer"> @@ -50,13 +53,12 @@ <button id="configureButton" hidden="true"></button> <button id="cancelButton" hidden="true"></button> <button id="connectButton" class="primary" hidden="true"></button> - <button id="tryBridgeButton" class="primary" hidden="true"></button> - <div id="locationDropdownLabel"/> + <label id="locationDropdownLabel" for="countries"/> <form id="locationDropdown" hidden="true"> <select id="countries"> </select> </form> - <button id="tryAgainButton" class="primary" hidden="true"></button> + <button id="tryBridgeButton" class="primary" hidden="true"></button> </div> </div> </div> diff --git a/browser/components/torconnect/content/globe.svg b/browser/components/torconnect/content/globe.svg deleted file mode 100644 index f4d1f19b43ce8..0000000000000 --- a/browser/components/torconnect/content/globe.svg +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M8 0.5C6.01088 0.5 4.10322 1.29018 2.6967 2.6967C1.29018 4.10322 0.5 6.01088 0.5 8C0.5 9.98912 1.29018 11.8968 2.6967 13.3033C4.10322 14.7098 6.01088 15.5 8 15.5C9.98912 15.5 11.8968 14.7098 13.3033 13.3033C14.7098 11.8968 15.5 9.98912 15.5 8C15.5 6.01088 14.7098 4.10322 13.3033 2.6967C11.8968 1.29018 9.98912 0.5 8 0.5ZM10.447 2.25C11.4738 2.69088 12.3637 3.39877 13.0242 4.30006C13.6848 5.20135 14.0918 6.26313 14.203 7.375H11.974C11.8509 5.51288 11.1778 3.72922 10.04 2.25H10 [...] -</svg> diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn index 8ca0b0651523e..043bd1435cda5 100644 --- a/browser/components/torconnect/jar.mn +++ b/browser/components/torconnect/jar.mn @@ -5,7 +5,6 @@ browser.jar: content/browser/torconnect/aboutTorConnect.js (content/aboutTorConnect.js) content/browser/torconnect/arrow-right.svg (content/arrow-right.svg) content/browser/torconnect/bridge.svg (content/bridge.svg) - content/browser/torconnect/globe.svg (content/globe.svg) content/browser/torconnect/connection-failure.svg (content/connection-failure.svg) content/browser/torconnect/connection-location.svg (content/connection-location.svg) content/browser/torconnect/onion.svg (content/onion.svg) diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index 225429d95b667..73c48281276b9 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -219,7 +219,10 @@ let RemotePageAccessManager = { RPMSendQuery: ["FetchUpdateData"], }, "about:torconnect": { - RPMAddMessageListener: ["torconnect:state-change"], + RPMAddMessageListener: [ + "torconnect:state-change", + "torconnect:user-action", + ], RPMSendAsyncMessage: [ "torconnect:open-tor-preferences", "torconnect:begin-bootstrap", @@ -228,6 +231,8 @@ let RemotePageAccessManager = { "torconnect:set-quickstart", "torconnect:view-tor-logs", "torconnect:restart", + "torconnect:set-ui-state", + "torconnect:broadcast-user-action", ], RPMSendQuery: [ "torconnect:get-init-args",
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit fbff9f39c5664de83eafde828505882a263d66a7 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Fri May 13 12:43:15 2022 +0200
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
Fixes for bugs found in 91.9, to keep as a fixup also in 91.10.
Bug 40918: Hide breadcrumbs on the first bootstrap
Bug 40923: Redirect to location confirmation rather than location detection failed when Moat reports a country code but the bootstrap fails anyway. --- browser/components/torconnect/TorConnectParent.jsm | 5 +++ .../torconnect/content/aboutTorConnect.js | 40 +++++++++++++++------- 2 files changed, 32 insertions(+), 13 deletions(-)
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm index 3c2a56934c145..cf3c1233dd622 100644 --- a/browser/components/torconnect/TorConnectParent.jsm +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -36,7 +36,9 @@ class TorConnectParent extends JSWindowActorParent { BootstrapProgress: TorConnect.bootstrapProgress, BootstrapStatus: TorConnect.bootstrapStatus, InternetStatus: TorConnect.internetStatus, + DetectedLocation: TorConnect.detectedLocation, ShowViewLog: TorConnect.logHasWarningOrError, + HasBootsrapEverFailed: TorConnect.hasBootstrapEverFailed, QuickStartEnabled: TorSettings.quickstart.enabled, UIState: TorConnect.uiState, }; @@ -65,6 +67,8 @@ class TorConnectParent extends JSWindowActorParent { self.state.ErrorMessage = null; self.state.ErrorDetails = null; } + self.state.HasBootsrapEverFailed = + TorConnect.hasBootstrapEverFailed; break; } case TorConnectTopics.BootstrapProgress: { @@ -81,6 +85,7 @@ class TorConnectParent extends JSWindowActorParent { self.state.ErrorMessage = obj.message; self.state.ErrorDetails = obj.details; self.state.InternetStatus = TorConnect.internetStatus; + self.state.DetectedLocation = TorConnect.detectedLocation; self.state.ShowViewLog = true; break; } diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js index 8b5f7216e9ef8..6b33442c7d02b 100644 --- a/browser/components/torconnect/content/aboutTorConnect.js +++ b/browser/components/torconnect/content/aboutTorConnect.js @@ -403,12 +403,17 @@ class AboutTorConnect { this.transitionUIState(UIStates.ConnectionAssist, state); } else if (state.PreviousState === TorConnectState.AutoBootstrapping) { if (this.uiState.bootstrapCause === UIStates.ConnectionAssist) { - this.transitionUIState( - this.getLocation() === "automatic" - ? UIStates.CouldNotLocate - : UIStates.LocationConfirm, - state - ); + if (this.getLocation() === "automatic") { + this.uiState.allowAutomaticLocation = false; + if (!state.DetectedLocation) { + this.transitionUIState(UIStates.CouldNotLocate, state); + return; + } + // Change the location only here, to avoid overriding any user change/ + // insisting with the detected location + this.setLocation(state.DetectedLocation); + } + this.transitionUIState(UIStates.LocationConfirm, state); } else { this.transitionUIState(UIStates.FinalError, state); } @@ -499,7 +504,11 @@ class AboutTorConnect { this.setTitle(title, ""); this.showConfigureConnectionLink(description); this.setProgress("", showProgressbar, state.BootstrapProgress); - this.setBreadcrumbsStatus(...breadcrumbs); + if (state.HasBootsrapEverFailed) { + this.setBreadcrumbsStatus(...breadcrumbs); + } else { + this.hideBreadcrumbs(); + } this.hideButtons(); if (state.ShowViewLog) { this.show(this.elements.viewLogContainer); @@ -622,7 +631,7 @@ class AboutTorConnect { RPMSendQuery("torconnect:get-country-codes").then(codes => { if (codes && codes.length) { this.populateFrequentLocations(codes); - this.setLocationFromState(); + this.setLocation(); } }); let firstOpt = this.elements.locationDropdownSelect.options[0]; @@ -633,7 +642,7 @@ class AboutTorConnect { firstOpt.value = ""; firstOpt.textContent = TorStrings.torConnect.selectCountryRegion; } - this.setLocationFromState(); + this.setLocation(); this.validateLocation(); this.show(this.elements.locationDropdownLabel); this.show(this.elements.locationDropdown); @@ -650,8 +659,13 @@ class AboutTorConnect { return this.elements.locationDropdownSelect.options[selectedIndex].value; }
- setLocationFromState() { - if (this.getLocation() === this.uiState.selectedLocation) { + setLocation(code) { + if (!code) { + code = this.uiState.selectedLocation; + } else { + this.uiState.selectedLocation = code; + } + if (this.getLocation() === code) { return; } const options = this.elements.locationDropdownSelect.options; @@ -659,7 +673,7 @@ class AboutTorConnect { // the .value way to select (which would however require the label, // rather than the code)... for (let i = 0; i < options.length; i++) { - if (options[i].value === this.uiState.selectedLocation) { + if (options[i].value === code) { this.elements.locationDropdownSelect.selectedIndex = i; break; } @@ -770,7 +784,7 @@ class AboutTorConnect { RPMAddMessageListener("torconnect:user-action", ({ data }) => { if (data.location) { this.uiState.selectedLocation = data.location; - this.setLocationFromState(); + this.setLocation(); } if (data.uiState !== undefined) { this.transitionUIState(data.uiState, data.connState);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit efff98ff20dba3e4c8d369c59fd4463e9b340876 Author: Arthur Edelstein arthuredelstein@gmail.com AuthorDate: Wed Aug 27 16:25:00 2014 -0700
Bug 12620: TorBrowser regression tests
Regression tests for Bug #2950: Make Permissions Manager memory-only
Regression tests for TB4: Tor Browser's Firefox preference overrides.
Note: many more functional tests could be made here
Regression tests for #2874: Block Components.interfaces from content
Bug 18923: Add a script to run all Tor Browser specific tests
Regression tests for Bug #16441: Suppress "Reset Tor Browser" prompt. --- run-tbb-tests | 66 +++++++++++++++++++++++++++++++++++ tbb-tests-ignore.txt | 13 +++++++ tbb-tests/browser.ini | 5 +++ tbb-tests/browser_tor_TB4.js | 35 +++++++++++++++++++ tbb-tests/browser_tor_bug2950.js | 74 ++++++++++++++++++++++++++++++++++++++++ tbb-tests/mochitest.ini | 3 ++ tbb-tests/moz.build | 9 +++++ tbb-tests/test_tor_bug2874.html | 25 ++++++++++++++ toolkit/toolkit.mozbuild | 3 +- 9 files changed, 232 insertions(+), 1 deletion(-)
diff --git a/run-tbb-tests b/run-tbb-tests new file mode 100755 index 0000000000000..bc09839f9f05d --- /dev/null +++ b/run-tbb-tests @@ -0,0 +1,66 @@ +#!/bin/bash + +# This script runs all the Mochitest tests that have been added or +# modified since the last ffxbld commit. +# +# It does not currently run XPCShell tests. We should change this if we +# start using this type or other types of tests. +# +# The logs of the tests are stored in the tbb-tests.log file. +# Ignored tests are listed in the tbb-tests-ignore.txt file. +# +# https://trac.torproject.org/projects/tor/ticket/18923 + +IFS=$'\n' + +if [ -n "$USE_TESTS_LIST" ] && [ -f tbb-tests-list.txt ] +then + echo "Using tests list from file tbb-tests-list.txt" + tests=($(cat tbb-tests-list.txt)) +else + ffxbld_commit=$(git log -500 --format='oneline' | grep "TB3: Tor Browser's official .mozconfigs." \ + | head -1 | cut -d ' ' -f 1) + + tests=($(git diff --name-status "$ffxbld_commit" HEAD | \ + grep -e '^[AM].*/test_[^/]+.(html|xul)$' \ + -e '^[AM].*/browser_[^/]+.js$' \ + | sed 's/^[AM]\s+//')) +fi + +echo 'The following tests will be run:' +for i in "${!tests[@]}" +do + if [ -z "$USE_TESTS_LIST" ] \ + && grep -q "^${tests[$i]}$" tbb-tests-ignore.txt + then + unset "tests[$i]" + continue + fi + echo "- ${tests[$i]}" +done + +if [ -n "$WRITE_TESTS_LIST" ] +then + rm -f tbb-tests-list.txt + for i in "${!tests[@]}" + do + echo "${tests[$i]}" >> tbb-tests-list.txt + done + exit 0 +fi + +rm -f tbb-tests.log +echo $'\n''Starting tests' +# We need `security.nocertdb = false` because of #18087. That pref is +# forced to have the same value as `browser.privatebrowsing.autostart` in +# torbutton, so we just set `browser.privatebrowsing.autostart=false` here. +./mach mochitest --log-tbpl tbb-tests.log \ + --setpref network.file.path_blacklist='' \ + --setpref extensions.torbutton.use_nontor_proxy=true \ + --setpref browser.privatebrowsing.autostart=false \ + "${tests[@]}" + +echo "*************************" +echo "*************************" +echo "Summary of failed tests:" +grep --color=never TEST-UNEXPECTED-FAIL tbb-tests.log diff --git a/tbb-tests-ignore.txt b/tbb-tests-ignore.txt new file mode 100644 index 0000000000000..ee3927a9e7c4c --- /dev/null +++ b/tbb-tests-ignore.txt @@ -0,0 +1,13 @@ +browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js +browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js +browser/extensions/onboarding/test/browser/browser_onboarding_notification.js +browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js +browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js +browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js +browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js +browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js +browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js +browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js +browser/extensions/onboarding/test/browser/browser_onboarding_tours.js +browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js +browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js diff --git a/tbb-tests/browser.ini b/tbb-tests/browser.ini new file mode 100644 index 0000000000000..f481660f14170 --- /dev/null +++ b/tbb-tests/browser.ini @@ -0,0 +1,5 @@ +[DEFAULT] + +[browser_tor_bug2950.js] +[browser_tor_omnibox.js] +[browser_tor_TB4.js] diff --git a/tbb-tests/browser_tor_TB4.js b/tbb-tests/browser_tor_TB4.js new file mode 100644 index 0000000000000..8bb12f360e5ea --- /dev/null +++ b/tbb-tests/browser_tor_TB4.js @@ -0,0 +1,35 @@ +// # Test for TB4: Tor Browser's Firefox preference overrides +// This is a minimal test to check whether the 000-tor-browser.js +// pref overrides are being used at all or not. More comprehensive +// pref tests are maintained in the tor-browser-bundle-testsuite project. + +function test() { + +let expectedPrefs = [ + // Homepage + ["browser.startup.homepage", "about:tor"], + + // Disable the "Refresh" prompt that is displayed for stale profiles. + ["browser.disableResetPrompt", true], + + // Version placeholder + ["torbrowser.version", "dev-build"], + ]; + +let getPref = function (prefName) { + let type = Services.prefs.getPrefType(prefName); + if (type === Services.prefs.PREF_INT) return Services.prefs.getIntPref(prefName); + if (type === Services.prefs.PREF_BOOL) return Services.prefs.getBoolPref(prefName); + if (type === Services.prefs.PREF_STRING) return Services.prefs.getCharPref(prefName); + // Something went wrong. + throw new Error("Can't access pref " + prefName); +}; + +let testPref = function([key, expectedValue]) { + let foundValue = getPref(key); + is(foundValue, expectedValue, "Pref '" + key + "' should be '" + expectedValue +"'."); +}; + +expectedPrefs.map(testPref); + +} // end function test() diff --git a/tbb-tests/browser_tor_bug2950.js b/tbb-tests/browser_tor_bug2950.js new file mode 100644 index 0000000000000..16e41344a3c42 --- /dev/null +++ b/tbb-tests/browser_tor_bug2950.js @@ -0,0 +1,74 @@ +// # Regression tests for tor Bug #2950, Make Permissions Manager memory-only +// Ensures that permissions.sqlite file in profile directory is not written to, +// even when we write a value to Firefox's permissions database. + +// The requisite test() function. +function test() { + +// Needed because of asynchronous part later in the test. +waitForExplicitFinish(); + +// Shortcut +let Ci = Components.interfaces; + +// ## utility functions + +// __principal(spec)__. +// Creates a principal instance from a spec +// (string address such as "https://www.torproject.org"). +let principal = spec => Services.scriptSecurityManager.createContentPrincipalFromOrigin(spec); + +// __setPermission(spec, key, value)__. +// Sets the site permission of type key to value, for the site located at address spec. +let setPermission = (spec, key, value) => SitePermissions.setForPrincipal(principal(spec), key, value); + +// __getPermission(spec, key)__. +// Reads the site permission value for permission type key, for the site +// located at address spec. +let getPermission = (spec, key) => SitePermissions.getForPrincipal(principal(spec), key); + +// __profileDirPath__. +// The Firefox Profile directory. Expected location of various persistent files. +let profileDirPath = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile).path; + +// __fileInProfile(fileName)__. +// Returns an nsIFile instance corresponding to a file in the Profile directory. +let fileInProfile = fileName => FileUtils.File(profileDirPath + "/" + fileName); + +// ## Now let's run the test. + +let SITE = "https://www.torproject.org", + KEY = "popup"; + +let permissionsFile = fileInProfile("permissions.sqlite"), + lastModifiedTime = null, + newModifiedTime = null; +if (permissionsFile.exists()) { + lastModifiedTime = permissionsFile.lastModifiedTime; +} +// Read the original value of the permission. +let originalValue = getPermission(SITE, KEY); + +// We need to delay by at least 1000 ms, because that's the granularity +// of file time stamps, it seems. +window.setTimeout( + function () { + // Set the permission to a new value. + setPermission(SITE, KEY, SitePermissions.BLOCK); + // Now read back the permission value again. + let newReadValue = getPermission(SITE, KEY); + // Compare to confirm that the permission + // value was successfully changed. + Assert.notDeepEqual(originalValue, newReadValue, "Set a value in permissions db (perhaps in memory)."); + // If file existed or now exists, get the current time stamp. + if (permissionsFile.exists()) { + newModifiedTime = permissionsFile.lastModifiedTime; + } + // If file was created or modified since we began this test, + // then permissions db is not memory only. Complain! + is(lastModifiedTime, newModifiedTime, "Don't write to permissions.sqlite file on disk."); + // We are done with the test. + finish(); + }, 1100); + +} // test() diff --git a/tbb-tests/mochitest.ini b/tbb-tests/mochitest.ini new file mode 100644 index 0000000000000..cc5172733bbed --- /dev/null +++ b/tbb-tests/mochitest.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_tor_bug2874.html] diff --git a/tbb-tests/moz.build b/tbb-tests/moz.build new file mode 100644 index 0000000000000..01db60b9c28ab --- /dev/null +++ b/tbb-tests/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += ["mochitest.ini"] + +BROWSER_CHROME_MANIFESTS += ["browser.ini"] diff --git a/tbb-tests/test_tor_bug2874.html b/tbb-tests/test_tor_bug2874.html new file mode 100644 index 0000000000000..c0a956e9f687e --- /dev/null +++ b/tbb-tests/test_tor_bug2874.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tor bug +https://trac.torproject.org/projects/tor/ticket/2874 +--> +<head> + <meta charset="utf-8"> + <title>Test for Tor Bug 2874</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + is(typeof Components, 'undefined', "The global window object should not expose a Components property to untrusted content."); + </script> +</head> +<body> +<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/2874">Tor Bug 2874</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild index 5576f6acc427f..a2c3ddf2dc381 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -99,7 +99,8 @@ if CONFIG['MOZ_WEBRTC'] and CONFIG['COMPILE_ENVIRONMENT']: ]
if CONFIG['ENABLE_TESTS']: - DIRS += ['/testing/specialpowers'] + DIRS += ['/testing/specialpowers', + '/tbb-tests']
DIRS += [ '/testing/gtest',
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 333f9f41a2d1c90a02bd5434d7327745d543a9f5 Author: Alex Catarineu acat@torproject.org AuthorDate: Fri Sep 4 12:34:35 2020 +0200
Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop
This loads HTTPS Everywhere as a builtin addon from a hardcoded resource:// URI in desktop. It also ensures that the non-builtin HTTPS Everywhere addon is always uninstalled on browser startup.
The reason of making this desktop-only is that there are some issues when installing a builtin extension from geckoview side, making the extension not available on first startup. So, at least for now we handle the Fenix case separately. See #40118 for a followup for investigating these. --- browser/components/BrowserGlue.jsm | 37 ++++++++++++++++++++++ toolkit/components/extensions/Extension.jsm | 10 ++++-- .../mozapps/extensions/internal/XPIProvider.jsm | 13 ++++++++ 3 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index dc956bc796166..7c81eaaccf775 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -45,6 +45,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { DownloadsViewableInternally: "resource:///modules/DownloadsViewableInternally.jsm", E10SUtils: "resource://gre/modules/E10SUtils.jsm", + ExtensionData: "resource://gre/modules/Extension.jsm", ExtensionsUI: "resource:///modules/ExtensionsUI.jsm", FeatureGate: "resource://featuregates/FeatureGate.jsm", FxAccounts: "resource://gre/modules/FxAccounts.jsm", @@ -119,6 +120,13 @@ XPCOMUtils.defineLazyServiceGetters(this, { PushService: ["@mozilla.org/push/Service;1", "nsIPushService"], });
+XPCOMUtils.defineLazyServiceGetters(this, { + resProto: [ + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler", + ], +}); + const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
/** @@ -1399,6 +1407,35 @@ BrowserGlue.prototype = { "resource://builtin-themes/alpenglow/" );
+ // Install https-everywhere builtin addon if needed. + (async () => { + const HTTPS_EVERYWHERE_ID = "https-everywhere-eff@eff.org"; + const HTTPS_EVERYWHERE_BUILTIN_URL = + "resource://torbutton/content/extensions/https-everywhere/"; + // This does something similar as GeckoViewWebExtension.jsm: it tries + // to load the manifest to retrieve the version of the builtin and + // compares it to the currently installed one to see whether we need + // to install or not. Here we delegate that to + // AddonManager.maybeInstallBuiltinAddon. + try { + const resolvedURI = Services.io.newURI( + resProto.resolveURI(Services.io.newURI(HTTPS_EVERYWHERE_BUILTIN_URL)) + ); + const extensionData = new ExtensionData(resolvedURI); + const manifest = await extensionData.loadManifest(); + + await AddonManager.maybeInstallBuiltinAddon( + HTTPS_EVERYWHERE_ID, + manifest.version, + HTTPS_EVERYWHERE_BUILTIN_URL + ); + } catch (e) { + const log = Log.repository.getLogger("HttpsEverywhereBuiltinLoader"); + log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); + log.error("Could not install https-everywhere extension", e); + } + })(); + if (AppConstants.MOZ_NORMANDY) { Normandy.init(); } diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 08c5cf8a9190d..783ec7c3391dc 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -267,6 +267,7 @@ const LOGGER_ID_BASE = "addons.webextension."; const UUID_MAP_PREF = "extensions.webextensions.uuids"; const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall"; const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall"; +const PERSISTENT_EXTENSIONS = new Set(["https-everywhere-eff@eff.org"]);
const COMMENT_REGEXP = new RegExp( String.raw` @@ -413,7 +414,8 @@ var ExtensionAddonObserver = { ); }
- if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)) { + if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) && + !PERSISTENT_EXTENSIONS.has(addon.id)) { // Clear browser.storage.local backends. AsyncShutdown.profileChangeTeardown.addBlocker( `Clear Extension Storage ${addon.id} (File Backend)`, @@ -461,7 +463,8 @@ var ExtensionAddonObserver = {
ExtensionPermissions.removeAll(addon.id);
- if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false)) { + if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false) && + !PERSISTENT_EXTENSIONS.has(addon.id)) { // Clear the entry in the UUID map UUIDMap.remove(addon.id); } @@ -2696,7 +2699,8 @@ class Extension extends ExtensionData { ); } else if ( this.startupReason === "ADDON_INSTALL" && - !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) + !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) && + !PERSISTENT_EXTENSIONS.has(this.id) ) { // If the extension has been just installed, set it as migrated, // because there will not be any data to migrate. diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 04d57a42348e7..fd1e3f75935da 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1495,6 +1495,19 @@ var XPIStates = { continue; }
+ // Uninstall HTTPS Everywhere if it is installed in the user profile. + if ( + id === "https-everywhere-eff@eff.org" && + loc.name === KEY_APP_PROFILE + ) { + logger.debug( + "Uninstalling the HTTPS Everywhere extension from user profile." + ); + loc.installer.uninstallAddon(id); + changed = true; + continue; + } + let xpiState = loc.get(id); if (!xpiState) { // If the location is not supported for sideloading, skip new
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 565b9ab2e6f47fa6c4677bb2d646641bec26649f Author: Matthew Finkel sysrqb@torproject.org AuthorDate: Fri Sep 3 14:58:28 2021 +0000
Bug 40253: Explicitly allow NoScript in Private Browsing mode. --- toolkit/components/extensions/Extension.jsm | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 783ec7c3391dc..1d72f88b276d1 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -2644,6 +2644,15 @@ class Extension extends ExtensionData { this.permissions.add(PRIVATE_ALLOWED_PERMISSION); }
+ // Bug 40253: Explicitly allow NoScript in Private Browsing mode. + if (this.id === "{73a6fe31-595d-460b-a920-fcc0f8843232}") { + ExtensionPermissions.add(this.id, { + permissions: [PRIVATE_ALLOWED_PERMISSION], + origins: [], + }); + this.permissions.add(PRIVATE_ALLOWED_PERMISSION); + } + // We only want to update the SVG_CONTEXT_PROPERTIES_PERMISSION during install and // upgrade/downgrade startups. if (INSTALL_AND_UPDATE_STARTUP_REASONS.has(this.startupReason)) {
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit ee931063ef7aa2f3e67210ea68edf16820ec6a1f Author: Richard Pospesel richard@torproject.org AuthorDate: Mon Mar 4 16:09:51 2019 -0800
Bug 25658: Replace security slider with security level UI
This patch adds a new 'securitylevel' component to Tor Browser intended to replace the torbutton 'Security Slider'.
This component adds a new Security Level toolbar button which visually indicates the current global security level via icon (as defined by the extensions.torbutton.security_slider pref), a drop-down hanger with a short description of the current security level, and a new section in the about:preferences#privacy page where users can change their current security level. In addition, the hanger and the preferences page will show a visual warning when the user has modified prefs associated with the security level and provide a one-click 'Restore Defaults' button to get the user back on recommended settings.
Strings used by this patch are pulled from the torbutton extension, but en-US defaults are provided if there is an error loading from the extension. With this patch applied, the usual work-flow of "./mach build && ./mach run" work as expected, even if the torbutton extension is disabled. --- browser/base/content/browser.js | 10 + browser/base/content/browser.xhtml | 2 + browser/base/content/main-popupset.inc.xhtml | 1 + browser/base/content/navigator-toolbox.inc.xhtml | 2 + browser/components/moz.build | 1 + browser/components/preferences/preferences.xhtml | 1 + browser/components/preferences/privacy.inc.xhtml | 2 + browser/components/preferences/privacy.js | 20 + .../securitylevel/content/securityLevel.js | 527 +++++++++++++++++++++ .../securitylevel/content/securityLevelButton.css | 18 + .../content/securityLevelButton.inc.xhtml | 7 + .../securitylevel/content/securityLevelIcon.svg | 40 ++ .../securitylevel/content/securityLevelPanel.css | 74 +++ .../content/securityLevelPanel.inc.xhtml | 47 ++ .../content/securityLevelPreferences.css | 52 ++ .../content/securityLevelPreferences.inc.xhtml | 67 +++ browser/components/securitylevel/jar.mn | 6 + browser/components/securitylevel/moz.build | 1 + browser/modules/TorStrings.jsm | 4 + .../themes/shared/customizableui/panelUI.inc.css | 3 +- 20 files changed, 884 insertions(+), 1 deletion(-)
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index ef8a191987676..abca251bf3aa6 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -224,6 +224,11 @@ XPCOMUtils.defineLazyScriptGetter( ["DownloadsButton", "DownloadsIndicatorView"], "chrome://browser/content/downloads/indicator.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["SecurityLevelButton"], + "chrome://browser/content/securitylevel/securityLevel.js" +); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", @@ -1760,6 +1765,9 @@ var gBrowserInit = { // doesn't flicker as the window is being shown. DownloadsButton.init();
+ // Init the SecuritySettingsButton + SecurityLevelButton.init(); + // Certain kinds of automigration rely on this notification to complete // their tasks BEFORE the browser window is shown. SessionStore uses it to // restore tabs into windows AFTER important parts like gMultiProcessBrowser @@ -2493,6 +2501,8 @@ var gBrowserInit = {
DownloadsButton.uninit();
+ SecurityLevelButton.uninit(); + TorBootstrapUrlbar.uninit();
gAccessibilityServiceIndicator.uninit(); diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index f163073657286..627e6ac0f8a09 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -21,6 +21,8 @@ <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/content/tabbrowser.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/content/downloads/downloads.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPanel.css"?> +<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelButton.css"?> <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml index e5bf9460b75d1..3fc665c65d793 100644 --- a/browser/base/content/main-popupset.inc.xhtml +++ b/browser/base/content/main-popupset.inc.xhtml @@ -520,6 +520,7 @@ #include ../../components/controlcenter/content/protectionsPanel.inc.xhtml #include ../../components/downloads/content/downloadsPanel.inc.xhtml #include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml +#include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml #include browser-allTabsMenu.inc.xhtml
<tooltip id="dynamic-shortcut-tooltip" diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index e7f63116ff39e..6ac72cb889bc0 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -413,6 +413,8 @@ </box> </toolbarbutton>
+#include ../../components/securitylevel/content/securityLevelButton.inc.xhtml + <toolbarbutton id="fxa-toolbar-menu-button" class="toolbarbutton-1 chromeclass-toolbar-additional subviewbutton-nav" badged="true" onmousedown="gSync.toggleAccountPanel(this, event)" diff --git a/browser/components/moz.build b/browser/components/moz.build index d15ff30515936..ec8fab7fbc8f7 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -48,6 +48,7 @@ DIRS += [ "protocolhandler", "resistfingerprinting", "search", + "securitylevel", "sessionstore", "shell", "syncedtabs", diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 9ee09581de32a..48836ec54a4e0 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -13,6 +13,7 @@ <?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?> +<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?> <?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [ diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index 8d51e60cb32be..d2cf2ea9c89c7 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -919,6 +919,8 @@ <html:h1 data-l10n-id="security-header"/> </hbox>
+#include ../securitylevel/content/securityLevelPreferences.inc.xhtml + <!-- addons, forgery (phishing) UI Security --> <groupbox id="browsingProtectionGroup" data-category="panePrivacy" hidden="true"> <label><html:h2 data-l10n-id="security-browsing-protection"/></label> diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js index 35b37b099e937..bce7bb7e8a9c6 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -80,6 +80,13 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() { } });
+// TODO: module import via ChromeUtils.defineModuleGetter +XPCOMUtils.defineLazyScriptGetter( + this, + ["SecurityLevelPreferences"], + "chrome://browser/content/securitylevel/securityLevel.js" +); + XPCOMUtils.defineLazyServiceGetter( this, "listManager", @@ -308,6 +315,18 @@ function setUpContentBlockingWarnings() { var gPrivacyPane = { _pane: null,
+ /** + * Show the Security Level UI + */ + _initSecurityLevel() { + SecurityLevelPreferences.init(); + let unload = () => { + window.removeEventListener("unload", unload); + SecurityLevelPreferences.uninit(); + }; + window.addEventListener("unload", unload); + }, + /** * Whether the prompt to restart Firefox should appear when changing the autostart pref. */ @@ -503,6 +522,7 @@ var gPrivacyPane = { this.trackingProtectionReadPrefs(); this.networkCookieBehaviorReadPrefs(); this._initTrackingProtectionExtensionControl(); + this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/components/securitylevel/content/securityLevel.js b/browser/components/securitylevel/content/securityLevel.js new file mode 100644 index 0000000000000..8b8babe5b58ef --- /dev/null +++ b/browser/components/securitylevel/content/securityLevel.js @@ -0,0 +1,527 @@ +"use strict"; + +ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +ChromeUtils.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + CustomizableUI: "resource:///modules/CustomizableUI.jsm", + PanelMultiView: "resource:///modules/PanelMultiView.jsm", +}); + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +/* + Security Level Prefs + + Getters and Setters for relevant torbutton prefs +*/ +const SecurityLevelPrefs = { + security_slider_pref : "extensions.torbutton.security_slider", + security_custom_pref : "extensions.torbutton.security_custom", + + get securitySlider() { + try { + return Services.prefs.getIntPref(this.security_slider_pref); + } catch(e) { + // init pref to 4 (standard) + const val = 4; + Services.prefs.setIntPref(this.security_slider_pref, val); + return val; + } + }, + + set securitySlider(val) { + Services.prefs.setIntPref(this.security_slider_pref, val); + }, + + get securityCustom() { + try { + return Services.prefs.getBoolPref(this.security_custom_pref); + } catch(e) { + // init custom to false + const val = false; + Services.prefs.setBoolPref(this.security_custom_pref, val); + return val; + } + }, + + set securityCustom(val) { + Services.prefs.setBoolPref(this.security_custom_pref, val); + }, +}; /* Security Level Prefs */ + +/* + Security Level Button Code + + Controls init and update of the security level toolbar button +*/ + +const SecurityLevelButton = { + _securityPrefsBranch : null, + + _populateXUL : function(securityLevelButton) { + if (securityLevelButton != null) { + securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.securityLevel); + securityLevelButton.setAttribute("label", TorStrings.securityLevel.securityLevel); + } + }, + + _configUIFromPrefs : function(securityLevelButton) { + if (securityLevelButton != null) { + let securitySlider = SecurityLevelPrefs.securitySlider; + securityLevelButton.removeAttribute("level"); + const securityCustom = SecurityLevelPrefs.securityCustom; + switch(securitySlider) { + case 4: + securityLevelButton.setAttribute("level", `standard${securityCustom ? "_custom" : ""}`); + securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.standard.tooltip); + break; + case 2: + securityLevelButton.setAttribute("level", `safer${securityCustom ? "_custom" : ""}`); + securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.safer.tooltip); + break; + case 1: + securityLevelButton.setAttribute("level", `safest${securityCustom ? "_custom" : ""}`); + securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.safest.tooltip); + break; + } + } + }, + + get button() { + let button = document.getElementById("security-level-button"); + if (!button) { + return null; + } + return button; + }, + + get anchor() { + let anchor = this.button.icon; + if (!anchor) { + return null; + } + + anchor.setAttribute("consumeanchor", SecurityLevelButton.button.id); + return anchor; + }, + + init : function() { + // set the initial class based off of the current pref + let button = this.button; + this._populateXUL(button); + this._configUIFromPrefs(button); + + this._securityPrefsBranch = Services.prefs.getBranch("extensions.torbutton."); + this._securityPrefsBranch.addObserver("", this, false); + + CustomizableUI.addListener(this); + + SecurityLevelPanel.init(); + }, + + uninit : function() { + CustomizableUI.removeListener(this); + + this._securityPrefsBranch.removeObserver("", this); + this._securityPrefsBranch = null; + + SecurityLevelPanel.uninit(); + }, + + observe : function(subject, topic, data) { + switch(topic) { + case "nsPref:changed": + if (data === "security_slider" || data === "security_custom") { + this._configUIFromPrefs(this.button); + } + break; + } + }, + + // callback for entering the 'Customize Firefox' screen to set icon + onCustomizeStart : function(window) { + let navigatorToolbox = document.getElementById("navigator-toolbox"); + let button = navigatorToolbox.palette.querySelector("#security-level-button"); + this._populateXUL(button); + this._configUIFromPrefs(button); + }, + + // callback when CustomizableUI modifies DOM + onWidgetAfterDOMChange : function(aNode, aNextNode, aContainer, aWasRemoval) { + if (aNode.id == "security-level-button" && !aWasRemoval) { + this._populateXUL(aNode); + this._configUIFromPrefs(aNode); + } + }, + + // for when the toolbar button needs to be activated and displays the Security Level panel + // + // In the toolbarbutton xul you'll notice we register this callback for both onkeypress and + // onmousedown. We do this to match the behavior of other panel spawning buttons such as Downloads, + // Library, and the Hamburger menus. Using oncommand alone would result in only getting fired + // after onclick, which is mousedown followed by mouseup. + onCommand : function(aEvent) { + // snippet borrowed from /browser/components/downloads/content/indicator.js DownloadsIndicatorView.onCommand(evt) + if ( + // On Mac, ctrl-click will send a context menu event from the widget, so + // we don't want to bring up the panel when ctrl key is pressed. + (aEvent.type == "mousedown" && + (aEvent.button != 0 || + (AppConstants.platform == "macosx" && aEvent.ctrlKey))) || + (aEvent.type == "keypress" && aEvent.key != " " && aEvent.key != "Enter") + ) { + return; + } + + // we need to set this attribute for the button to be shaded correctly to look like it is pressed + // while the security level panel is open + this.button.setAttribute("open", "true"); + SecurityLevelPanel.show(); + aEvent.stopPropagation(); + }, +}; /* Security Level Button */ + +/* + Security Level Panel Code + + Controls init and update of the panel in the security level hanger +*/ + +const SecurityLevelPanel = { + _securityPrefsBranch : null, + _panel : null, + _anchor : null, + _populated : false, + + _selectors: Object.freeze({ + panel: "panel#securityLevel-panel", + icon: "vbox#securityLevel-vbox>vbox", + header: "h1#securityLevel-header", + level: "label#securityLevel-level", + custom: "label#securityLevel-custom", + summary: "description#securityLevel-summary", + learnMore: "label#securityLevel-learnMore", + restoreDefaults: "button#securityLevel-restoreDefaults", + advancedSecuritySettings: "button#securityLevel-advancedSecuritySettings", + }), + + _populateXUL : function() { + let selectors = this._selectors; + + this._elements = { + panel: document.querySelector(selectors.panel), + icon: document.querySelector(selectors.icon), + header: document.querySelector(selectors.header), + levelLabel: document.querySelector(selectors.level), + customLabel: document.querySelector(selectors.custom), + summaryDescription: document.querySelector(selectors.summary), + learnMoreLabel: document.querySelector(selectors.learnMore), + restoreDefaultsButton: document.querySelector(selectors.restoreDefaults), + changeButton: document.querySelector(selectors.advancedSecuritySettings), + }; + let elements = this._elements; + + elements.header.textContent = TorStrings.securityLevel.securityLevel; + elements.customLabel.setAttribute("value", TorStrings.securityLevel.customWarning); + elements.learnMoreLabel.setAttribute("value", TorStrings.securityLevel.learnMore); + elements.learnMoreLabel.setAttribute("href", TorStrings.securityLevel.learnMoreURL); + elements.restoreDefaultsButton.setAttribute("label", TorStrings.securityLevel.restoreDefaults); + elements.changeButton.setAttribute("label", TorStrings.securityLevel.change); + + this._configUIFromPrefs(); + this._populated = true; + }, + + _configUIFromPrefs : function() { + // get security prefs + let securitySlider = SecurityLevelPrefs.securitySlider; + let securityCustom = SecurityLevelPrefs.securityCustom; + + // get the panel elements we need to populate + let elements = this._elements; + let icon = elements.icon; + let labelLevel = elements.levelLabel; + let labelCustomWarning = elements.customLabel; + let summary = elements.summaryDescription; + let buttonRestoreDefaults = elements.restoreDefaultsButton; + let buttonAdvancedSecuritySettings = elements.changeButton; + + // only visible when user is using custom settings + labelCustomWarning.hidden = !securityCustom; + buttonRestoreDefaults.hidden = !securityCustom; + + // Descriptions change based on security level + switch(securitySlider) { + // standard + case 4: + icon.setAttribute("level", "standard"); + labelLevel.setAttribute("value", TorStrings.securityLevel.standard.level); + summary.textContent = TorStrings.securityLevel.standard.summary; + break; + // safer + case 2: + icon.setAttribute("level", "safer"); + labelLevel.setAttribute("value", TorStrings.securityLevel.safer.level); + summary.textContent = TorStrings.securityLevel.safer.summary; + break; + // safest + case 1: + icon.setAttribute("level", "safest"); + labelLevel.setAttribute("value", TorStrings.securityLevel.safest.level); + summary.textContent = TorStrings.securityLevel.safest.summary; + break; + } + + // override the summary text with custom warning + if (securityCustom) { + summary.textContent = TorStrings.securityLevel.custom.summary; + } + }, + + init : function() { + this._securityPrefsBranch = Services.prefs.getBranch("extensions.torbutton."); + this._securityPrefsBranch.addObserver("", this, false); + }, + + uninit : function() { + this._securityPrefsBranch.removeObserver("", this); + this._securityPrefsBranch = null; + }, + + show : function() { + // we have to defer this until after the browser has finished init'ing before + // we can populate the panel + if (!this._populated) { + this._populateXUL(); + } + + let panel = document.getElementById("securityLevel-panel"); + panel.hidden = false; + PanelMultiView.openPopup(panel, SecurityLevelButton.anchor, "bottomcenter topright", + 0, 0, false, null).catch(Cu.reportError); + }, + + hide : function() { + let panel = document.getElementById("securityLevel-panel"); + PanelMultiView.hidePopup(panel); + }, + + restoreDefaults : function() { + SecurityLevelPrefs.securityCustom = false; + // hide and reshow so that layout re-renders properly + this.hide(); + this.show(this._anchor); + }, + + openAdvancedSecuritySettings : function() { + openPreferences("privacy-securitylevel"); + this.hide(); + }, + + // callback when prefs change + observe : function(subject, topic, data) { + switch(topic) { + case "nsPref:changed": + if (data == "security_slider" || data == "security_custom") { + this._configUIFromPrefs(); + } + break; + } + }, + + // callback when the panel is displayed + onPopupShown : function(event) { + SecurityLevelButton.button.setAttribute("open", "true"); + }, + + // callback when the panel is hidden + onPopupHidden : function(event) { + SecurityLevelButton.button.removeAttribute("open"); + } +}; /* Security Level Panel */ + +/* + Security Level Preferences Code + + Code to handle init and update of security level section in about:preferences#privacy +*/ + +const SecurityLevelPreferences = +{ + _securityPrefsBranch : null, + + _populateXUL : function() { + let groupbox = document.getElementById("securityLevel-groupbox"); + + let labelHeader = groupbox.querySelector("#securityLevel-header"); + labelHeader.textContent = TorStrings.securityLevel.securityLevel; + + let spanOverview = groupbox.querySelector("#securityLevel-overview"); + spanOverview.textContent = TorStrings.securityLevel.overview; + + let labelLearnMore = groupbox.querySelector("#securityLevel-learnMore"); + labelLearnMore.setAttribute("value", TorStrings.securityLevel.learnMore); + labelLearnMore.setAttribute("href", TorStrings.securityLevel.learnMoreURL); + + let radiogroup = document.getElementById("securityLevel-radiogroup"); + radiogroup.addEventListener("command", SecurityLevelPreferences.selectSecurityLevel); + + let populateRadioElements = function(vboxQuery, stringStruct) { + let vbox = groupbox.querySelector(vboxQuery); + + let radio = vbox.querySelector("radio"); + radio.setAttribute("label", stringStruct.level); + + let customWarning = vbox.querySelector("#securityLevel-customWarning"); + customWarning.setAttribute("value", TorStrings.securityLevel.customWarning); + + let labelSummary = vbox.querySelector("#securityLevel-summary"); + labelSummary.textContent = stringStruct.summary; + + let labelRestoreDefaults = vbox.querySelector("#securityLevel-restoreDefaults"); + labelRestoreDefaults.setAttribute("value", TorStrings.securityLevel.restoreDefaults); + labelRestoreDefaults.addEventListener("click", SecurityLevelPreferences.restoreDefaults); + + let description1 = vbox.querySelector("#securityLevel-description1"); + if (description1) { + description1.textContent = stringStruct.description1; + } + let description2 = vbox.querySelector("#securityLevel-description2"); + if (description2) { + description2.textContent = stringStruct.description2; + } + let description3 = vbox.querySelector("#securityLevel-description3"); + if (description3) { + description3.textContent = stringStruct.description3; + } + }; + + populateRadioElements("#securityLevel-vbox-standard", TorStrings.securityLevel.standard); + populateRadioElements("#securityLevel-vbox-safer", TorStrings.securityLevel.safer); + populateRadioElements("#securityLevel-vbox-safest", TorStrings.securityLevel.safest); + }, + + _configUIFromPrefs : function() { + // read our prefs + let securitySlider = SecurityLevelPrefs.securitySlider; + let securityCustom = SecurityLevelPrefs.securityCustom; + + // get our elements + let groupbox = document.getElementById("securityLevel-groupbox"); + + let radiogroup = groupbox.querySelector("#securityLevel-radiogroup"); + let labelStandardCustom = groupbox.querySelector("#securityLevel-vbox-standard label#securityLevel-customWarning"); + let labelSaferCustom = groupbox.querySelector("#securityLevel-vbox-safer label#securityLevel-customWarning"); + let labelSafestCustom = groupbox.querySelector("#securityLevel-vbox-safest label#securityLevel-customWarning"); + let labelStandardRestoreDefaults = groupbox.querySelector("#securityLevel-vbox-standard label#securityLevel-restoreDefaults"); + let labelSaferRestoreDefaults = groupbox.querySelector("#securityLevel-vbox-safer label#securityLevel-restoreDefaults"); + let labelSafestRestoreDefaults = groupbox.querySelector("#securityLevel-vbox-safest label#securityLevel-restoreDefaults"); + + // hide custom label by default until we know which level we're at + labelStandardCustom.hidden = true; + labelSaferCustom.hidden = true; + labelSafestCustom.hidden = true; + + labelStandardRestoreDefaults.hidden = true; + labelSaferRestoreDefaults.hidden = true; + labelSafestRestoreDefaults.hidden = true; + + switch(securitySlider) { + // standard + case 4: + radiogroup.value = "standard"; + labelStandardCustom.hidden = !securityCustom; + labelStandardRestoreDefaults.hidden = !securityCustom; + break; + // safer + case 2: + radiogroup.value = "safer"; + labelSaferCustom.hidden = !securityCustom; + labelSaferRestoreDefaults.hidden = !securityCustom; + break; + // safest + case 1: + radiogroup.value = "safest"; + labelSafestCustom.hidden = !securityCustom; + labelSafestRestoreDefaults.hidden = !securityCustom; + break; + } + }, + + init : function() { + // populate XUL with localized strings + this._populateXUL(); + + // read prefs and populate UI + this._configUIFromPrefs(); + + // register for pref chagnes + this._securityPrefsBranch = Services.prefs.getBranch("extensions.torbutton."); + this._securityPrefsBranch.addObserver("", this, false); + }, + + uninit : function() { + // unregister for pref change events + this._securityPrefsBranch.removeObserver("", this); + this._securityPrefsBranch = null; + }, + + // callback for when prefs change + observe : function(subject, topic, data) { + switch(topic) { + case "nsPref:changed": + if (data == "security_slider" || + data == "security_custom") { + this._configUIFromPrefs(); + } + break; + } + }, + + selectSecurityLevel : function() { + // radio group elements + let radiogroup = document.getElementById("securityLevel-radiogroup"); + + // update pref based on selected radio option + switch (radiogroup.value) { + case "standard": + SecurityLevelPrefs.securitySlider = 4; + break; + case "safer": + SecurityLevelPrefs.securitySlider = 2; + break; + case "safest": + SecurityLevelPrefs.securitySlider = 1; + break; + } + + SecurityLevelPreferences.restoreDefaults(); + }, + + restoreDefaults : function() { + SecurityLevelPrefs.securityCustom = false; + }, +}; /* Security Level Prefereces */ + +Object.defineProperty(this, "SecurityLevelButton", { + value: SecurityLevelButton, + enumerable: true, + writable: false +}); + +Object.defineProperty(this, "SecurityLevelPanel", { + value: SecurityLevelPanel, + enumerable: true, + writable: false +}); + +Object.defineProperty(this, "SecurityLevelPreferences", { + value: SecurityLevelPreferences, + enumerable: true, + writable: false +}); diff --git a/browser/components/securitylevel/content/securityLevelButton.css b/browser/components/securitylevel/content/securityLevelButton.css new file mode 100644 index 0000000000000..38701250e9c96 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelButton.css @@ -0,0 +1,18 @@ +toolbarbutton#security-level-button[level="standard"] { + list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard"); +} +toolbarbutton#security-level-button[level="safer"] { + list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer"); +} +toolbarbutton#security-level-button[level="safest"] { + list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest"); +} +toolbarbutton#security-level-button[level="standard_custom"] { + list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom"); +} +toolbarbutton#security-level-button[level="safer_custom"] { + list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer_custom"); +} +toolbarbutton#security-level-button[level="safest_custom"] { + list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest_custom"); +} \ No newline at end of file diff --git a/browser/components/securitylevel/content/securityLevelButton.inc.xhtml b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml new file mode 100644 index 0000000000000..96ee1ec0ca499 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml @@ -0,0 +1,7 @@ +<toolbarbutton id="security-level-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + badged="true" + removable="true" + onmousedown="SecurityLevelButton.onCommand(event);" + onkeypress="SecurityLevelButton.onCommand(event);" + closemenu="none" + cui-areatype="toolbar"/> diff --git a/browser/components/securitylevel/content/securityLevelIcon.svg b/browser/components/securitylevel/content/securityLevelIcon.svg new file mode 100644 index 0000000000000..38cdbcb68afc3 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelIcon.svg @@ -0,0 +1,40 @@ +<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <style> + use:not(:target) { + display: none; + } + </style> + <defs> + <g id="standard_icon" stroke="none" stroke-width="1"> + <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd" /> + </g> + <g id="safer_icon" stroke="none" stroke-width="1"> + <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd"/> + <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4-2.28572c.16666-.09523.37403.02511.37403.21707v10.0766c-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/> + </g> + <g id="safest_icon" stroke="none" stroke-width="1"> + <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd"/> + <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4.25-2.42857c.07685-.04392.17121-.04392.24806 0l4.24997 2.42857c.0779.04451.126.12734.126.21706v.40411c0 1.43511-.5582 3.23363-1.5795 4.77628-.8665 1.3087-1.90846 2.2025-2.9205 2.6105-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/> + </g> + <g id="standard_custom_icon" stroke="none" stroke-width="1"> + <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342- [...] + <circle cx="13" cy="3" fill="#ffbd2e" r="3"/> + </g> + <g id="safer_custom_icon" stroke="none" stroke-width="1"> + <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342- [...] + <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4-2.28572c.16666-.09523.37403.02511.37403.21707v10.0766c-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/> + <circle cx="13" cy="3" fill="#ffbd2e" r="3"/> + </g> + <g id="safest_custom_icon" stroke="none" stroke-width="1"> + <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342- [...] + <path d="m8.77266 3.44151-.64863-.37064c-.07685-.04392-.17121-.04392-.24806 0l-4.25 2.42857c-.0779.04451-.12597.12735-.12597.21706v.40412c0 1.4351.55818 3.23362 1.57952 4.77618.86648 1.3087 1.90844 2.2026 2.92048 2.6106 1.01204-.408 2.054-1.3018 2.9205-2.6106.7761-1.17217 1.2847-2.49215 1.4843-3.68816-1.9219-.26934-3.43158-1.82403-3.63214-3.76713z"/> + <circle cx="13" cy="3" fill="#ffbd2e" r="3"/> + </g> + </defs> + <use id="standard" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_icon" /> + <use id="safer" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_icon" /> + <use id="safest" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_icon" /> + <use id="standard_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_custom_icon" /> + <use id="safer_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_custom_icon" /> + <use id="safest_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_custom_icon" /> +</svg> diff --git a/browser/components/securitylevel/content/securityLevelPanel.css b/browser/components/securitylevel/content/securityLevelPanel.css new file mode 100644 index 0000000000000..6462c02f15942 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPanel.css @@ -0,0 +1,74 @@ +/* Security Level CSS */ + +panelview#securityLevel-panelview { + width: 25em; +} + +vbox#securityLevel-vbox > vbox { + background-repeat: no-repeat; + /* icon center-line should be in-line with right margin */ + /* -margin + panelWidth - imageWidth/2 */ + background-position: calc(-16px + 25em - 4.5em) 0.4em; + background-size: 9em 9em; + -moz-context-properties: fill, fill-opacity; + fill-opacity: 1; + fill: var(--button-bgcolor); + min-height: 10em; +} + +vbox#securityLevel-vbox > vbox[level="standard"] { + background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard"); +} +vbox#securityLevel-vbox > vbox[level="safer"] { + background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer"); +} +vbox#securityLevel-vbox > vbox[level="safest"] { + background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest"); +} + +vbox#securityLevel-vbox > toolbarseparator { + margin-inline: 16px; +} + +vbox#securityLevel-vbox > vbox { + margin-inline: 0; + padding-inline: 16px; +} + +vbox#securityLevel-vbox > vbox * { + margin-inline: 0; +} + +vbox#securityLevel-vbox > vbox > hbox { +} + +label#securityLevel-level { + font-size: 1.25em; + font-weight: 600; + padding-top: 0.15em; +} + +label#securityLevel-custom { + border-radius: 4px; + background-color: var(--yellow-50); + color: black; + font-size: 1em; + height: 1.6em; + line-height: 1.0em; + padding: 0.4em 0.5em; + margin-left: 1em!important; +} + +description#securityLevel-summary { + margin-top: 1em; + padding-right: 5em; +} + +vbox#securityLevel-vbox > hbox.panel-footer { + display: flex; +} + + +button#securityLevel-advancedSecuritySettings { + margin-block: 0; +} diff --git a/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml new file mode 100644 index 0000000000000..02d93b738ff50 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml @@ -0,0 +1,47 @@ +<panel id="securityLevel-panel" + role="group" + type="arrow" + orient="vertical" + level="top" + hidden="true" + class="panel-no-padding" + onpopupshown="SecurityLevelPanel.onPopupShown(event);" + onpopuphidden="SecurityLevelPanel.onPopupHidden(event);"> + <panelmultiview mainViewId="securityLevel-panelview"> + <panelview id="securityLevel-panelview" descriptionheightworkaround="true"> + <vbox id="securityLevel-vbox"> + <box class="panel-header"> + <html:h1 id="securityLevel-header"/> + </box> + <toolbarseparator></toolbarseparator> + <vbox> + <hbox> + <label id="securityLevel-level"/> + <vbox> + <spacer flex="1"/> + <label id="securityLevel-custom"/> + <spacer flex="1"/> + </vbox> + <spacer flex="1"/> + </hbox> + <description id="securityLevel-summary"/> + <hbox> + <label + id="securityLevel-learnMore" + class="learnMore text-link" + onclick="SecurityLevelPanel.hide();" + is="text-link"/> + <spacer/> + </hbox> + </vbox> + <hbox class="panel-footer"> + <button id="securityLevel-restoreDefaults" + oncommand="SecurityLevelPanel.restoreDefaults();"/> + <button id="securityLevel-advancedSecuritySettings" + default="true" + oncommand="SecurityLevelPanel.openAdvancedSecuritySettings();"/> + </hbox> + </vbox> + </panelview> + </panelmultiview> +</panel> diff --git a/browser/components/securitylevel/content/securityLevelPreferences.css b/browser/components/securitylevel/content/securityLevelPreferences.css new file mode 100644 index 0000000000000..12a7cccffe099 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPreferences.css @@ -0,0 +1,52 @@ +label#securityLevel-customWarning { + border-radius: 4px; + background-color: var(--yellow-50); + color: black; + font-size: 1em; + height: 1.6em; + padding: 0.4em 0.5em; +} + +radiogroup#securityLevel-radiogroup description { + color: var(--in-content-page-color)!important; +} + +radiogroup#securityLevel-radiogroup radio { + font-weight: bold; +} + +radiogroup#securityLevel-radiogroup > vbox { + border: 1px solid var(--in-content-box-border-color); + border-radius: 4px; + margin: 3px 0; + padding: 9px; +} + +radiogroup#securityLevel-radiogroup[value=standard] > vbox#securityLevel-vbox-standard, +radiogroup#securityLevel-radiogroup[value=safer] > vbox#securityLevel-vbox-safer, +radiogroup#securityLevel-radiogroup[value=safest] > vbox#securityLevel-vbox-safest { + --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent); + background-color: var(--section-highlight-background-color); + border: 1px solid var(--in-content-accent-color); + +} + +vbox#securityLevel-descriptionList { + display: none; + margin-inline-start: +} + +radiogroup#securityLevel-radiogroup[value=safer] > vbox#securityLevel-vbox-safer > vbox#securityLevel-descriptionList, +radiogroup#securityLevel-radiogroup[value=safest] > vbox#securityLevel-vbox-safest > vbox#securityLevel-descriptionList { + display: inherit; +} + +vbox#securityLevel-descriptionList > description { + display: list-item; +} + +vbox#securityLevel-vbox-standard, +vbox#securityLevel-vbox-safer, +vbox#securityLevel-vbox-safest { + margin-top: 0.4em; +} diff --git a/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml new file mode 100644 index 0000000000000..b050dad81621a --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml @@ -0,0 +1,67 @@ +<groupbox id="securityLevel-groupbox" data-category="panePrivacy" hidden="true"> + <label><html:h2 id="securityLevel-header"/></label> + <vbox data-subcategory="securitylevel" flex="1"> + <description flex="1"> + <html:span id="securityLevel-overview" class="tail-with-learn-more"/> + <label id="securityLevel-learnMore" class="learnMore text-link" is="text-link"/> + </description> + <radiogroup id="securityLevel-radiogroup"> + <vbox id="securityLevel-vbox-standard"> + <hbox> + <radio value="standard"/> + <vbox> + <spacer flex="1"/> + <label id="securityLevel-customWarning"/> + <spacer flex="1"/> + </vbox> + <spacer flex="1"/> + </hbox> + <description flex="1" class="indent"> + <html:span id="securityLevel-summary" class="tail-with-learn-more"/> + <label id="securityLevel-restoreDefaults" + class="learnMore text-link"/> + </description> + </vbox> + <vbox id="securityLevel-vbox-safer"> + <hbox> + <radio value="safer"/> + <vbox> + <spacer flex="1"/> + <label id="securityLevel-customWarning"/> + <spacer flex="1"/> + </vbox> + </hbox> + <description flex="1" class="indent"> + <html:span id="securityLevel-summary" class="tail-with-learn-more"/> + <label id="securityLevel-restoreDefaults" + class="learnMore text-link"/> + </description> + <vbox id="securityLevel-descriptionList" class="indent"> + <description id="securityLevel-description1" class="indent"/> + <description id="securityLevel-description2" class="indent"/> + <description id="securityLevel-description3" class="indent"/> + </vbox> + </vbox> + <vbox id="securityLevel-vbox-safest"> + <hbox> + <radio value="safest"/> + <vbox> + <spacer flex="1"/> + <label id="securityLevel-customWarning"/> + <spacer flex="1"/> + </vbox> + </hbox> + <description flex="1" class="indent"> + <html:span id="securityLevel-summary" class="tail-with-learn-more"/> + <label id="securityLevel-restoreDefaults" + class="learnMore text-link"/> + </description> + <vbox id="securityLevel-descriptionList" class="indent"> + <description id="securityLevel-description1" class="indent"/> + <description id="securityLevel-description2" class="indent"/> + <description id="securityLevel-description3" class="indent"/> + </vbox> + </vbox> + </radiogroup> + </vbox> +</groupbox> diff --git a/browser/components/securitylevel/jar.mn b/browser/components/securitylevel/jar.mn new file mode 100644 index 0000000000000..61aa4169f9ece --- /dev/null +++ b/browser/components/securitylevel/jar.mn @@ -0,0 +1,6 @@ +browser.jar: + content/browser/securitylevel/securityLevel.js (content/securityLevel.js) + content/browser/securitylevel/securityLevelPanel.css (content/securityLevelPanel.css) + content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css) + content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css) + content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg) diff --git a/browser/components/securitylevel/moz.build b/browser/components/securitylevel/moz.build new file mode 100644 index 0000000000000..2661ad7cb9f3d --- /dev/null +++ b/browser/components/securitylevel/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm index f4e54cfad3443..1586a3f787e39 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -230,6 +230,10 @@ var TorStrings = { "advanced_security_settings", "Advanced Security Settings\u2026" ), + change: getString( + "change", + "Change\u2026" + ), }; return retval; })() /* Security Level Strings */, diff --git a/browser/themes/shared/customizableui/panelUI.inc.css b/browser/themes/shared/customizableui/panelUI.inc.css index e1d64c707518b..abecf34cdb923 100644 --- a/browser/themes/shared/customizableui/panelUI.inc.css +++ b/browser/themes/shared/customizableui/panelUI.inc.css @@ -1430,7 +1430,8 @@ menuitem.panel-subview-footer@menuStateActive@, #editBookmarkPanel toolbarseparator, #downloadsPanel-mainView toolbarseparator, .cui-widget-panelview menuseparator, -.cui-widget-panel toolbarseparator { +.cui-widget-panel toolbarseparator, +#securityLevel-panel toolbarseparator { appearance: none; min-height: 0; border-top: 1px solid var(--panel-separator-color);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit b304b5f34b3938a562393e4c274796432cc0d627 Author: Alex Catarineu acat@torproject.org AuthorDate: Fri Oct 4 19:08:33 2019 +0200
Bug 27511: Add new identity button to toolbar
Also added 'New circuit for this site' button to CustomizableUI, but not visible by default. --- browser/base/content/navigator-toolbox.inc.xhtml | 10 ++++++++++ .../components/customizableui/CustomizableUI.jsm | 21 +++++++++++++++++++++ browser/themes/shared/icons/new_circuit.svg | 6 ++++++ browser/themes/shared/icons/new_identity.svg | 9 +++++++++ browser/themes/shared/jar.inc.mn | 3 +++ browser/themes/shared/toolbarbutton-icons.inc.css | 8 ++++++++ 6 files changed, 57 insertions(+)
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 6ac72cb889bc0..e10e0580b8ecb 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -573,6 +573,16 @@ ondragenter="newWindowButtonObserver.onDragOver(event)" ondragexit="newWindowButtonObserver.onDragExit(event)"/>
+ <toolbarbutton id="new-identity-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + label="&torbutton.context_menu.new_identity;" + oncommand="torbutton_new_identity();" + tooltiptext="&torbutton.context_menu.new_identity;"/> + + <toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + label="&torbutton.context_menu.new_circuit;" + oncommand="torbutton_new_circuit();" + tooltiptext="&torbutton.context_menu.new_circuit;"/> + <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional" observes="View:FullScreen" type="checkbox" diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm index 8649d93347c49..5c5ab909b9a46 100644 --- a/browser/components/customizableui/CustomizableUI.jsm +++ b/browser/components/customizableui/CustomizableUI.jsm @@ -79,6 +79,8 @@ const kSubviewEvents = ["ViewShowing", "ViewHiding"]; */ var kVersion = 17;
+var kTorVersion = 1; + /** * Buttons removed from built-ins by version they were removed. kVersion must be * bumped any time a new id is added to this. Use the button id as key, and @@ -619,6 +621,20 @@ var CustomizableUIInternal = { navbarPlacements.splice(newPosition, 0, "save-to-pocket-button"); } } + + let currentTorVersion = gSavedState.currentTorVersion; + if (currentTorVersion < 1 && gSavedState.placements) { + let navbarPlacements = gSavedState.placements[CustomizableUI.AREA_NAVBAR]; + if (navbarPlacements) { + let secLevelIndex = navbarPlacements.indexOf("security-level-button"); + if (secLevelIndex === -1) { + let urlbarIndex = navbarPlacements.indexOf("urlbar-container"); + secLevelIndex = urlbarIndex + 1; + navbarPlacements.splice(secLevelIndex, 0, "security-level-button"); + } + navbarPlacements.splice(secLevelIndex + 1, 0, "new-identity-button"); + } + } },
_updateForNewProtonVersion() { @@ -2528,6 +2544,10 @@ var CustomizableUIInternal = { gSavedState.currentVersion = 0; }
+ if (!("currentTorVersion" in gSavedState)) { + gSavedState.currentTorVersion = 0; + } + gSeenWidgets = new Set(gSavedState.seen || []); gDirtyAreaCache = new Set(gSavedState.dirtyAreaCache || []); gNewElementCount = gSavedState.newElementCount || 0; @@ -2606,6 +2626,7 @@ var CustomizableUIInternal = { seen: gSeenWidgets, dirtyAreaCache: gDirtyAreaCache, currentVersion: kVersion, + currentTorVersion: kTorVersion, newElementCount: gNewElementCount, };
diff --git a/browser/themes/shared/icons/new_circuit.svg b/browser/themes/shared/icons/new_circuit.svg new file mode 100644 index 0000000000000..ddc8199468181 --- /dev/null +++ b/browser/themes/shared/icons/new_circuit.svg @@ -0,0 +1,6 @@ +<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <g stroke="none" stroke-width="1" fill="context-fill" fill-rule="evenodd" opacity="context-fill-opacity"> + <path d="m10.707 6h3.993l.3-.3v-3.993c.0002-.09902-.0291-.19586-.084-.27825s-.1331-.14661-.2245-.18453c-.0915-.03792-.1922-.04782-.2893-.02845-.0971.01936-.1863.06713-.2562.13723l-1.459 1.459c-1.2817-1.16743-2.95335-1.813714-4.687-1.812-3.859 0-7 3.141-7 7s3.141 7 7 7c1.74123.007 3.422-.6379 4.7116-1.8079 1.2896-1.1701 2.0945-2.7804 2.2564-4.5141.0156-.1649-.0348-.32927-.1401-.4571s-.2571-.2087-.4219-.2249c-.1644-.01324-.3275.03801-.4548.1429s-.2088.2552-.2272.4191c-.1334 1.42392 [...] + <path d="m8 12.5c-2.48528 0-4.5-2.0147-4.5-4.5 0-2.48528 2.01472-4.5 4.5-4.5z"/> + </g> +</svg> diff --git a/browser/themes/shared/icons/new_identity.svg b/browser/themes/shared/icons/new_identity.svg new file mode 100644 index 0000000000000..096ff169c02f3 --- /dev/null +++ b/browser/themes/shared/icons/new_identity.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <g fill="context-fill" fill-opacity="context-fill-opacity"> + <path d="m13.5383 14.5627c-.1712-.0053-.3194-.1334-.3505-.3028-.0419-.294-.1441-.5789-.3001-.8369-.2583-.1558-.5436-.2579-.838-.2998-.1694-.0313-.2974-.1793-.3026-.3501-.0053-.1708.1136-.3146.2813-.3402.2944-.0329.5762-.1254.8284-.272.1426-.2476.2313-.5243.2608-.8129.0237-.1679.1662-.2884.3372-.2851.1699.0042.3181.1295.3517.2973.0471.2931.1533.5763.312.8323.2565.1573.5396.263.8326.3109.1682.0345.2929.1836.2958.3536.0028.17-.1171.3116-.2843.3357-.2894.0285-.5669.1172-.8147.2604-.1 [...] + <path d="m6.49858 2.99992c-.14675-.00459-.27377-.11436-.3004-.25961-.03593-.25196-.12354-.49621-.25729-.71731-.22137-.13358-.46594-.22109-.71822-.25699-.14526-.02682-.25492-.15363-.25945-.30004-.00454-.14641.09737-.26967.24112-.29164.25236-.02817.49393-.10747.71013-.233093.12217-.2123.19825-.449454.22353-.696834.0203-.143878.14242-.24714456.28897-.24434753.14565.00358504.27273.11100153.30149.25484453.0404.251183.13139.493923.2674.713349.21988.134841.46256.225461.71364.266481.1441 [...] + <path d="m1.82093 5.3609c-.15279-.00473-.28512-.11875-.31315-.26981-.02739-.18014-.08781-.35525-.1782-.51643-.16152-.09021-.336989-.15052-.517512-.17788-.151437-.02794-.265749-.16003-.270474-.31254-.004724-.15251.101518-.2809.251381-.30378.181146-.02145.355265-.07593.513815-.16075.08209-.15545.13363-.32622.15197-.50355.02095-.15059.14903-.25861.3025-.25512.15164.00368.28404.11525.31428.26484.03021.18029.09338.35503.18632.51538.16048.09192.33508.15452.51517.18469.1503.0308.26181.1 [...] + <path clip-rule="evenodd" d="m15.3213 1.06694c.2441-.244076.2441-.639804 0-.883882-.2441-.2440775-.6398-.2440774-.8839 0l-5.96506 5.965062h-.50519c-1.996-1.09517-4.49023.42233-6.49079 1.63948-.41545.25277-.80961.49258-1.173597.69335-.16756.10002-.289261.26641-.30145394.48048-.01219156.21407.06079654.41038.21802994.56743l1.243691 1.24224 2.37084-1.02603c.15392-.06661.30331.14022.18601.25753l-1.66213 1.6621 1.46329 1.4616 1.66126-1.6613c.1173-.1173.32413.0321.25752.186l-1.02482 2.3 [...] + </g> +</svg> diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn index f76184171ddd7..b2e469b90aa80 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -236,3 +236,6 @@ skin/classic/browser/places/tree-icons.css (../shared/places/tree-icons.css) skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css) skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg) + + skin/classic/browser/new_circuit.svg (../shared/icons/new_circuit.svg) + skin/classic/browser/new_identity.svg (../shared/icons/new_identity.svg) diff --git a/browser/themes/shared/toolbarbutton-icons.inc.css b/browser/themes/shared/toolbarbutton-icons.inc.css index 76d3f4212406e..e3e6f64869995 100644 --- a/browser/themes/shared/toolbarbutton-icons.inc.css +++ b/browser/themes/shared/toolbarbutton-icons.inc.css @@ -253,6 +253,14 @@ toolbar[brighttext]:-moz-lwtheme { list-style-image: url("chrome://browser/skin/new-tab.svg"); }
+#new-identity-button { + list-style-image: url("chrome://browser/skin/new_identity.svg"); +} + +#new-circuit-button { + list-style-image: url("chrome://browser/skin/new_circuit.svg"); +} + #privatebrowsing-button { list-style-image: url("chrome://browser/skin/privateBrowsing.svg"); }
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 57f2c989bc5c78ed49974151a310b8f7b8fb543a Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Fri Jan 13 11:40:24 2017 -0500
Bug 4234: Use the Firefox Update Process for Tor Browser.
The following files are never updated: TorBrowser/Data/Browser/profiles.ini TorBrowser/Data/Browser/profile.default/bookmarks.html TorBrowser/Data/Tor/torrc Mac OS: Store update metadata under TorBrowser/UpdateInfo. Removed the %OS_VERSION% component from the update URL (13047) and added support for minSupportedOSVersion, an attribute of the <update> element that may be used to trigger Firefox's "unsupported platform" behavior. Hide the "What's new" links (set app.releaseNotesURL value to about:blank). Windows: disable "runas" code path in updater (15201). Windows: avoid writing to the registry (16236). Also includes fixes for tickets 13047, 13301, 13356, 13594, 15406, 16014, 16909, 24476, and 25909.
Also fix Bug 26049: reduce the delay before the update prompt is displayed. Instead of Firefox's 2 days, we use 1 hour (after which time the update doorhanger will be displayed).
Also fix bug 27221: purge the startup cache if the Tor Browser version changed (even if the Firefox version and build ID did not change), e.g., after a minor Tor Browser update.
Also fix 32616: Disable GetSecureOutputDirectoryPath() functionality.
Bug 26048: potentially confusing "restart to update" message
Within the update doorhanger, remove the misleading message that mentions that windows will be restored after an update is applied, and replace the "Restart and Restore" button label with an existing "Restart to update Tor Browser" string.
Bug 28885: notify users that update is downloading
Add a "Downloading Tor Browser update" item which appears in the hamburger (app) menu while the update service is downloading a MAR file. Before this change, the browser did not indicate to the user that an update was in progress, which is especially confusing in Tor Browser because downloads often take some time. If the user clicks on the new menu item, the about dialog is opened to allow the user to see download progress.
As part of this fix, the update service was changed to always show update-related messages in the hamburger menu, even if the update was started in the foreground via the about dialog or via the "Check for Tor Browser Update" toolbar menu item. This change is consistent with the Tor Browser goal of making sure users are informed about the update process.
Removed #28885 parts of this patch which have been uplifted to Firefox. --- browser/app/Makefile.in | 2 + browser/app/profile/firefox.js | 10 +- browser/base/content/aboutDialog-appUpdater.js | 2 +- browser/base/content/aboutDialog.js | 12 +- browser/components/BrowserContentHandler.jsm | 39 ++- .../customizableui/content/panelUI.inc.xhtml | 2 +- browser/confvars.sh | 35 +-- browser/installer/package-manifest.in | 2 + build/application.ini.in | 2 +- build/moz.configure/init.configure | 3 +- config/createprecomplete.py | 18 +- .../client/aboutdebugging/src/actions/runtimes.js | 5 + toolkit/modules/UpdateUtils.jsm | 22 +- toolkit/mozapps/extensions/AddonManager.jsm | 24 ++ toolkit/mozapps/extensions/test/browser/head.js | 1 + .../extensions/test/xpcshell/head_addons.js | 1 + toolkit/mozapps/update/UpdateService.jsm | 125 +++++++- toolkit/mozapps/update/UpdateServiceStub.jsm | 4 + toolkit/mozapps/update/common/updatehelper.cpp | 8 + toolkit/mozapps/update/moz.build | 5 +- toolkit/mozapps/update/updater/launchchild_osx.mm | 2 + toolkit/mozapps/update/updater/moz.build | 2 +- toolkit/mozapps/update/updater/updater.cpp | 339 ++++++++++++++++++--- toolkit/xre/MacLaunchHelper.h | 2 + toolkit/xre/MacLaunchHelper.mm | 2 + toolkit/xre/nsAppRunner.cpp | 22 +- toolkit/xre/nsUpdateDriver.cpp | 109 ++++++- toolkit/xre/nsXREDirProvider.cpp | 38 ++- tools/update-packaging/common.sh | 64 ++-- tools/update-packaging/make_full_update.sh | 25 ++ tools/update-packaging/make_incremental_update.sh | 71 ++++- 31 files changed, 858 insertions(+), 140 deletions(-)
diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in index 8dd3a9a656612..3a5550c96c155 100644 --- a/browser/app/Makefile.in +++ b/browser/app/Makefile.in @@ -97,10 +97,12 @@ tools repackage:: $(DIST)/bin/$(MOZ_APP_NAME) $(objdir)/macbuild/Contents/MacOS- rsync -aL $(DIST)/bin/$(MOZ_APP_NAME) '$(dist_dest)/Contents/MacOS' cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/firefox.icns '$(dist_dest)/Contents/Resources/firefox.icns' cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/document.icns '$(dist_dest)/Contents/Resources/document.icns' +ifndef TOR_BROWSER_UPDATE $(MKDIR) -p '$(dist_dest)/Contents/Library/LaunchServices' ifdef MOZ_UPDATER mv -f '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' '$(dist_dest)/Contents/Library/LaunchServices' ln -s ../../../../Library/LaunchServices/org.mozilla.updater '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' +endif endif printf APPLTORB > '$(dist_dest)/Contents/PkgInfo' endif diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 3f90fcad36029..944a68bcd67a1 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -130,14 +130,8 @@ pref("app.update.elevation.promptMaxAttempts", 2); pref("app.update.notifyDuringDownload", false);
// If set to true, the Update Service will automatically download updates if the -// user can apply updates. This pref is no longer used on Windows, except as the -// default value to migrate to the new location that this data is now stored -// (which is in a file in the update directory). Because of this, this pref -// should no longer be used directly. Instead, getAppUpdateAutoEnabled and -// getAppUpdateAutoEnabled from UpdateUtils.jsm should be used. -#ifndef XP_WIN - pref("app.update.auto", true); -#endif +// user can apply updates. +pref("app.update.auto", true);
// If set to true, the Update Service will apply updates in the background // when it finishes downloading them. diff --git a/browser/base/content/aboutDialog-appUpdater.js b/browser/base/content/aboutDialog-appUpdater.js index 652cb5ac53b1e..e38b4505c8be3 100644 --- a/browser/base/content/aboutDialog-appUpdater.js +++ b/browser/base/content/aboutDialog-appUpdater.js @@ -174,7 +174,7 @@ appUpdater.prototype = { if (aChildID == "downloadAndInstall") { let updateVersion = gAppUpdater.update.displayVersion; // Include the build ID if this is an "a#" (nightly or aurora) build - if (/a\d+$/.test(updateVersion)) { + if (!AppConstants.TOR_BROWSER_UPDATE && /a\d+$/.test(updateVersion)) { let buildID = gAppUpdater.update.buildID; let year = buildID.slice(0, 4); let month = buildID.slice(4, 6); diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js index 37a0ebc4f4a43..4e8a9195e6a80 100644 --- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -56,15 +56,13 @@ async function init(aEvent) { bits: Services.appinfo.is64Bit ? 64 : 32, };
+ // Adjust version text to show the Tor Browser version + versionAttributes.version = AppConstants.TOR_BROWSER_VERSION + + " (based on Mozilla Firefox " + + AppConstants.MOZ_APP_VERSION_DISPLAY + ")"; + let version = Services.appinfo.version; if (/a\d+$/.test(version)) { - versionId = "aboutDialog-version-nightly"; - let buildID = Services.appinfo.appBuildID; - let year = buildID.slice(0, 4); - let month = buildID.slice(4, 6); - let day = buildID.slice(6, 8); - versionAttributes.isodate = `${year}-${month}-${day}`; - document.getElementById("experimental").hidden = false; document.getElementById("communityDesc").hidden = true; } diff --git a/browser/components/BrowserContentHandler.jsm b/browser/components/BrowserContentHandler.jsm index 97417d86cd7fa..d8e24e6414479 100644 --- a/browser/components/BrowserContentHandler.jsm +++ b/browser/components/BrowserContentHandler.jsm @@ -44,6 +44,8 @@ XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal", () => ); XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
+const kTBSavedVersionPref = "browser.startup.homepage_override.torbrowser.version"; + // One-time startup homepage override configurations const ONCE_DOMAINS = ["mozilla.org", "firefox.com"]; const ONCE_PREF = "browser.startup.homepage_override.once"; @@ -102,7 +104,8 @@ const OVERRIDE_NEW_BUILD_ID = 3; * Returns: * OVERRIDE_NEW_PROFILE if this is the first run with a new profile. * OVERRIDE_NEW_MSTONE if this is the first run with a build with a different - * Gecko milestone (i.e. right after an upgrade). + * Gecko milestone or Tor Browser version (i.e. right + * after an upgrade). * OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the * same Gecko milestone (i.e. after a nightly upgrade). * OVERRIDE_NONE otherwise. @@ -119,6 +122,11 @@ function needHomepageOverride(prefb) {
var mstone = Services.appinfo.platformVersion;
+ var savedTBVersion = null; + try { + savedTBVersion = prefb.getCharPref(kTBSavedVersionPref); + } catch (e) {} + var savedBuildID = prefb.getCharPref( "browser.startup.homepage_override.buildID", "" @@ -140,7 +148,22 @@ function needHomepageOverride(prefb) {
prefb.setCharPref("browser.startup.homepage_override.mstone", mstone); prefb.setCharPref("browser.startup.homepage_override.buildID", buildID); - return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE; + prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION); + + // After an upgrade from an older release of Tor Browser (<= 5.5a1), the + // savedmstone will be undefined because those releases included the + // value "ignore" for the browser.startup.homepage_override.mstone pref. + // To correctly detect an upgrade vs. a new profile, we check for the + // presence of the "app.update.postupdate" pref. + let updated = prefb.prefHasUserValue("app.update.postupdate"); + return (savedmstone || updated) ? OVERRIDE_NEW_MSTONE + : OVERRIDE_NEW_PROFILE; + } + + if (AppConstants.TOR_BROWSER_VERSION != savedTBVersion) { + prefb.setCharPref("browser.startup.homepage_override.buildID", buildID); + prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION); + return OVERRIDE_NEW_MSTONE; }
if (buildID != savedBuildID) { @@ -624,6 +647,13 @@ nsBrowserContentHandler.prototype = { "browser.startup.homepage_override.buildID", "unknown" ); + + // We do the same for the Tor Browser version. + let old_tbversion = null; + try { + old_tbversion = prefb.getCharPref(kTBSavedVersionPref); + } catch (e) {} + override = needHomepageOverride(prefb); if (override != OVERRIDE_NONE) { switch (override) { @@ -650,9 +680,10 @@ nsBrowserContentHandler.prototype = { "startup.homepage_override_url" ); let update = UpdateManager.readyUpdate; + let old_version = old_tbversion ? old_tbversion: old_mstone; if ( update && - Services.vc.compare(update.appVersion, old_mstone) > 0 + Services.vc.compare(update.appVersion, old_version) > 0 ) { overridePage = getPostUpdateOverridePage(update, overridePage); // Send the update ping to signal that the update was successful. @@ -660,6 +691,8 @@ nsBrowserContentHandler.prototype = { }
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone); + overridePage = overridePage.replace("%OLD_TOR_BROWSER_VERSION%", + old_tbversion); break; case OVERRIDE_NEW_BUILD_ID: if (UpdateManager.readyUpdate) { diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/browser/components/customizableui/content/panelUI.inc.xhtml index dca3ae14a6ff9..e71acbf4ae658 100644 --- a/browser/components/customizableui/content/panelUI.inc.xhtml +++ b/browser/components/customizableui/content/panelUI.inc.xhtml @@ -153,7 +153,7 @@ hasicon="true" hidden="true"> <popupnotificationcontent id="update-restart-notification-content" orient="vertical"> - <description id="update-restart-description" data-lazy-l10n-id="appmenu-update-restart-message2"></description> + <description id="update-restart-description"> </description> </popupnotificationcontent> </popupnotification>
diff --git a/browser/confvars.sh b/browser/confvars.sh index 92871c9516f98..040a27e9b92df 100755 --- a/browser/confvars.sh +++ b/browser/confvars.sh @@ -6,26 +6,6 @@ MOZ_APP_VENDOR=Mozilla MOZ_UPDATER=1
-if test "$OS_ARCH" = "WINNT"; then - if ! test "$HAVE_64BIT_BUILD"; then - if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \ - "$MOZ_UPDATE_CHANNEL" = "nightly-try" -o \ - "$MOZ_UPDATE_CHANNEL" = "aurora" -o \ - "$MOZ_UPDATE_CHANNEL" = "beta" -o \ - "$MOZ_UPDATE_CHANNEL" = "release"; then - if ! test "$MOZ_DEBUG"; then - if ! test "$USE_STUB_INSTALLER"; then - # Expect USE_STUB_INSTALLER from taskcluster for downstream task consistency - echo "ERROR: STUB installer expected to be enabled but" - echo "ERROR: USE_STUB_INSTALLER is not specified in the environment" - exit 1 - fi - MOZ_STUB_INSTALLER=1 - fi - fi - fi -fi - BROWSER_CHROME_URL=chrome://browser/content/browser.xhtml
# MOZ_APP_DISPLAYNAME will be set by branding/configure.sh @@ -38,6 +18,21 @@ MOZ_BRANDING_DIRECTORY=browser/branding/unofficial MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+# ACCEPTED_MAR_CHANNEL_IDS should usually be the same as the value MAR_CHANNEL_ID. +# If more than one ID is needed, then you should use a comma separated list +# of values. +# The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t " +if test "$MOZ_UPDATE_CHANNEL" = "alpha"; then + ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-alpha + MAR_CHANNEL_ID=torbrowser-torproject-alpha +elif test "$MOZ_UPDATE_CHANNEL" = "nightly"; then + ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-nightly + MAR_CHANNEL_ID=torbrowser-torproject-nightly +else + ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-release + MAR_CHANNEL_ID=torbrowser-torproject-release +fi + MOZ_PROFILE_MIGRATOR=1
# Include the DevTools client, not just the server (which is the default) diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index d46707ca87204..8009a4d83ec5f 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -36,8 +36,10 @@ ; Mac bundle stuff @APPNAME@/Contents/Info.plist #ifdef MOZ_UPDATER +#ifndef TOR_BROWSER_UPDATE @APPNAME@/Contents/Library/LaunchServices #endif +#endif @APPNAME@/Contents/PkgInfo @RESPATH@/firefox.icns @RESPATH@/document.icns diff --git a/build/application.ini.in b/build/application.ini.in index 6df13230a45b2..9a0b162d447d3 100644 --- a/build/application.ini.in +++ b/build/application.ini.in @@ -52,5 +52,5 @@ ServerURL=@MOZ_CRASHREPORTER_URL@/submit?id=@MOZ_APP_ID@&version=@MOZ_APP_VERSIO
#if MOZ_UPDATER [AppUpdate] -URL=https://@MOZ_APPUPDATE_HOST@/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_... +URL=https://aus1.torproject.org/torbrowser/update_3/%CHANNEL%/%BUILD_TARGET%/%VE... #endif diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure index 3a164c6558719..0c2c00faa5932 100644 --- a/build/moz.configure/init.configure +++ b/build/moz.configure/init.configure @@ -1177,7 +1177,6 @@ def version_path(path): # set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in # The logic works like this: # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD) -# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora # - otherwise, we're building Release/Beta (define RELEASE_OR_BETA) @depends(check_build_environment, build_project, version_path, "--help") @imports(_from="__builtin__", _import="open") @@ -1224,7 +1223,7 @@ def milestone(build_env, build_project, version_path, _):
if "a1" in milestone: is_nightly = True - elif "a" not in milestone: + else: is_release_or_beta = True
major_version = milestone.split(".")[0] diff --git a/config/createprecomplete.py b/config/createprecomplete.py index dda4efcdf8e19..e7405b21b61bf 100644 --- a/config/createprecomplete.py +++ b/config/createprecomplete.py @@ -5,6 +5,7 @@ # update instructions which is used to remove files and directories that are no # longer present in a complete update. The current working directory is used for # the location to enumerate and to create the precomplete file. +# For symlinks, remove instructions are always generated.
from __future__ import absolute_import from __future__ import unicode_literals @@ -13,9 +14,17 @@ import os import io
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove all lines in this file that contain: +# TorBrowser/Data + def get_build_entries(root_path): """Iterates through the root_path, creating a list for each file and directory. Excludes any file paths ending with channel-prefs.js. + To support Tor Browser updates, excludes: + TorBrowser/Data/Browser/profiles.ini + TorBrowser/Data/Browser/profile.default/bookmarks.html + TorBrowser/Data/Tor/torrc """ rel_file_path_set = set() rel_dir_path_set = set() @@ -27,6 +36,10 @@ def get_build_entries(root_path): if not ( rel_path_file.endswith("channel-prefs.js") or rel_path_file.endswith("update-settings.ini") + or rel_path_file == "TorBrowser/Data/Browser/profiles.ini" + or rel_path_file + == "TorBrowser/Data/Browser/profile.default/bookmarks.html" + or rel_path_file == "TorBrowser/Data/Tor/torrc" or rel_path_file.find("distribution/") != -1 ): rel_file_path_set.add(rel_path_file) @@ -36,7 +49,10 @@ def get_build_entries(root_path): rel_path_dir = os.path.join(parent_dir_rel_path, dir_name) rel_path_dir = rel_path_dir.replace("\", "/") + "/" if rel_path_dir.find("distribution/") == -1: - rel_dir_path_set.add(rel_path_dir) + if os.path.islink(rel_path_dir[:-1]): + rel_file_path_set.add(rel_path_dir[:-1]) + else: + rel_dir_path_set.add(rel_path_dir)
rel_file_path_list = list(rel_file_path_set) rel_file_path_list.sort(reverse=True) diff --git a/devtools/client/aboutdebugging/src/actions/runtimes.js b/devtools/client/aboutdebugging/src/actions/runtimes.js index 3d9ce0490bf25..00dff36f28a2c 100644 --- a/devtools/client/aboutdebugging/src/actions/runtimes.js +++ b/devtools/client/aboutdebugging/src/actions/runtimes.js @@ -71,6 +71,11 @@ async function getRuntimeIcon(runtime, channel) { } }
+ // Use the release build skin for devtools within Tor Browser alpha releases. + if (channel === "alpha") { + return "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg"; + } + return channel === "release" || channel === "beta" || channel === "aurora" ? `chrome://devtools/skin/images/aboutdebugging-firefox-${channel}.svg` : "chrome://devtools/skin/images/aboutdebugging-firefox-nightly.svg"; diff --git a/toolkit/modules/UpdateUtils.jsm b/toolkit/modules/UpdateUtils.jsm index a1a2ac7898fbd..fa840d6c0b2d0 100644 --- a/toolkit/modules/UpdateUtils.jsm +++ b/toolkit/modules/UpdateUtils.jsm @@ -96,7 +96,7 @@ var UpdateUtils = { case "PRODUCT": return Services.appinfo.name; case "VERSION": - return Services.appinfo.version; + return AppConstants.TOR_BROWSER_VERSION; case "BUILD_ID": return Services.appinfo.appBuildID; case "BUILD_TARGET": @@ -160,7 +160,8 @@ var UpdateUtils = { * downloads and installs updates. This corresponds to whether or not the user * has selected "Automatically install updates" in about:preferences. * - * On Windows, this setting is shared across all profiles for the installation + * On Windows (except in Tor Browser), this setting is shared across all profiles + * for the installation * and is read asynchronously from the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * @@ -176,7 +177,8 @@ var UpdateUtils = { * updates" and "Check for updates but let you choose to install them" options * in about:preferences. * - * On Windows, this setting is shared across all profiles for the installation + * On Windows (except in Tor Browser), this setting is shared across all profiles + * for the installation * and is written asynchronously to the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * @@ -248,7 +250,7 @@ var UpdateUtils = { // setting is just to propagate it from a pref observer. This ensures that // the expected observers still get notified, even if a user manually // changes the pref value. - if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) { + if (AppConstants.TOR_BROWSER_UPDATE || !UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) { let initialConfig = {}; for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS @@ -317,7 +319,7 @@ var UpdateUtils = { } }
- if (!this.PER_INSTALLATION_PREFS_SUPPORTED) { + if (AppConstants.TOR_BROWSER_UPDATE || !this.PER_INSTALLATION_PREFS_SUPPORTED) { // If we don't have per-installation prefs, we use regular preferences. let prefValue = prefTypeFns.getProfilePref(prefName, pref.defaultValue); return Promise.resolve(prefValue); @@ -413,7 +415,7 @@ var UpdateUtils = { ); }
- if (!this.PER_INSTALLATION_PREFS_SUPPORTED) { + if (AppConstants.TOR_BROWSER_UPDATE || !this.PER_INSTALLATION_PREFS_SUPPORTED) { // If we don't have per-installation prefs, we use regular preferences. if (options.setDefaultOnly) { prefTypeFns.setProfileDefaultPref(prefName, value); @@ -549,14 +551,6 @@ UpdateUtils.PER_INSTALLATION_PREFS = { migrate: true, observerTopic: "auto-update-config-change", policyFn: () => { - if (!Services.policies.isAllowed("app-auto-updates-off")) { - // We aren't allowed to turn off auto-update - it is forced on. - return true; - } - if (!Services.policies.isAllowed("app-auto-updates-on")) { - // We aren't allowed to turn on auto-update - it is forced off. - return false; - } return null; }, }, diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 36d2220064cd7..e2ccb5cc1d14e 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -23,6 +23,7 @@ const { AppConstants } = ChromeUtils.import(
const MOZ_COMPATIBILITY_NIGHTLY = ![ "aurora", + "alpha", "beta", "release", "esr", @@ -37,6 +38,7 @@ const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; const PREF_SYS_ADDON_UPDATE_ENABLED = "extensions.systemAddon.update.enabled"; +const PREF_EM_LAST_TORBROWSER_VERSION = "extensions.lastTorBrowserVersion";
const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; @@ -689,6 +691,28 @@ var AddonManagerInternal = { ); }
+ // To ensure that extension and plugin code gets a chance to run + // after each browser update, set appChanged = true when the + // Tor Browser version has changed even if the Mozilla app + // version has not changed. + let tbChanged = undefined; + try { + tbChanged = AppConstants.TOR_BROWSER_VERSION != + Services.prefs.getCharPref(PREF_EM_LAST_TORBROWSER_VERSION); + } + catch (e) { } + if (tbChanged !== false) { + // Because PREF_EM_LAST_TORBROWSER_VERSION was not present in older + // versions of Tor Browser, an app change is indicated when tbChanged + // is undefined or true. + if (appChanged === false) { + appChanged = true; + } + + Services.prefs.setCharPref(PREF_EM_LAST_TORBROWSER_VERSION, + AppConstants.TOR_BROWSER_VERSION); + } + if (!MOZ_COMPATIBILITY_NIGHTLY) { PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js index 32ce769afbc9d..740ae52877f43 100644 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -43,6 +43,7 @@ var PREF_CHECK_COMPATIBILITY; var channel = Services.prefs.getCharPref("app.update.channel", "default"); if ( channel != "aurora" && + channel != "alpha" && channel != "beta" && channel != "release" && channel != "esr" diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 439ecec5bd1f4..a716eba87e02c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -390,6 +390,7 @@ function isNightlyChannel() {
return ( channel != "aurora" && + channel != "alpha" && channel != "beta" && channel != "release" && channel != "esr" diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm index cd87b21b0ff9c..8552240a1df67 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -43,11 +43,15 @@ XPCOMUtils.defineLazyModuleGetters(this, { AddonManager: "resource://gre/modules/AddonManager.jsm", AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm", CertUtils: "resource://gre/modules/CertUtils.jsm", +#ifdef XP_WIN ctypes: "resource://gre/modules/ctypes.jsm", +#endif DeferredTask: "resource://gre/modules/DeferredTask.jsm", setTimeout: "resource://gre/modules/Timer.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", +#if !defined(TOR_BROWSER_UPDATE) WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm", +#endif });
if (AppConstants.ENABLE_WEBDRIVER) { @@ -525,6 +529,7 @@ function testWriteAccess(updateTestFile, createDirectory) { updateTestFile.remove(false); }
+#ifdef XP_WIN /** * Windows only function that closes a Win32 handle. * @@ -617,6 +622,7 @@ function getPerInstallationMutexName(aGlobal = true) { (aGlobal ? "Global\" : "") + "MozillaUpdateMutex-" + hasher.finish(true) ); } +#endif
/** * Whether or not the current instance has the update mutex. The update mutex @@ -627,6 +633,7 @@ function getPerInstallationMutexName(aGlobal = true) { * @return true if this instance holds the update mutex */ function hasUpdateMutex() { +#ifdef XP_WIN if (AppConstants.platform != "win") { return true; } @@ -634,6 +641,9 @@ function hasUpdateMutex() { gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false); } return !!gUpdateMutexHandle; +#else + return true; +#endif }
/** @@ -664,6 +674,11 @@ function areDirectoryEntriesWriteable(aDir) { * @return true if elevation is required, false otherwise */ function getElevationRequired() { +#if defined(TOR_BROWSER_UPDATE) + // To avoid potential security holes associated with running the updater + // process with elevated privileges, Tor Browser does not support elevation. + return false; +#else if (AppConstants.platform != "macosx") { return false; } @@ -698,6 +713,7 @@ function getElevationRequired() { "not required" ); return false; +#endif }
/** @@ -747,6 +763,7 @@ function getCanApplyUpdates() { return false; }
+#if !defined(TOR_BROWSER_UPDATE) if (AppConstants.platform == "macosx") { LOG( "getCanApplyUpdates - bypass the write since elevation can be used " + @@ -762,6 +779,7 @@ function getCanApplyUpdates() { ); return true; } +#endif
try { if (AppConstants.platform == "win") { @@ -1587,6 +1605,9 @@ function handleUpdateFailure(update, errorCode) { cancelations++; Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations); if (AppConstants.platform == "macosx") { +#if defined(TOR_BROWSER_UPDATE) + cleanupActiveUpdate(); +#else let osxCancelations = Services.prefs.getIntPref( PREF_APP_UPDATE_CANCELATIONS_OSX, 0 @@ -1610,6 +1631,7 @@ function handleUpdateFailure(update, errorCode) { (update.state = STATE_PENDING_ELEVATE) ); } +#endif update.statusText = gUpdateBundle.GetStringFromName("elevationFailure"); } else { writeStatusFile(getReadyUpdateDir(), (update.state = STATE_PENDING)); @@ -2198,7 +2220,26 @@ function Update(update) { this._patches.push(patch); }
- if (!this._patches.length && !update.hasAttribute("unsupported")) { + if (update.hasAttribute("unsupported")) { + this.unsupported = ("true" == update.getAttribute("unsupported")); + } else if (update.hasAttribute("minSupportedOSVersion")) { + let minOSVersion = update.getAttribute("minSupportedOSVersion"); + try { + let osVersion = Services.sysinfo.getProperty("version"); + this.unsupported = (Services.vc.compare(osVersion, minOSVersion) < 0); + } catch (e) {} + } + if (!this.unsupported && update.hasAttribute("minSupportedInstructionSet")) { + let minInstructionSet = update.getAttribute("minSupportedInstructionSet"); + if (['MMX', 'SSE', 'SSE2', 'SSE3', + 'SSE4A', 'SSE4_1', 'SSE4_2'].indexOf(minInstructionSet) >= 0) { + try { + this.unsupported = !Services.sysinfo.getProperty("has" + minInstructionSet); + } catch (e) {} + } + } + + if (!this._patches.length && !this.unsupported) { throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE); }
@@ -2236,9 +2277,7 @@ function Update(update) { if (!isNaN(attr.value)) { this.promptWaitTime = parseInt(attr.value); } - } else if (attr.name == "unsupported") { - this.unsupported = attr.value == "true"; - } else { + } else if (attr.name != "unsupported") { switch (attr.name) { case "appVersion": case "buildID": @@ -2263,7 +2302,11 @@ function Update(update) { }
if (!this.previousAppVersion) { +#ifdef TOR_BROWSER_UPDATE + this.previousAppVersion = AppConstants.TOR_BROWSER_VERSION; +#else this.previousAppVersion = Services.appinfo.version; +#endif }
if (!this.elevationFailure) { @@ -2651,6 +2694,7 @@ UpdateService.prototype = { Services.obs.removeObserver(this, topic); Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
+#ifdef XP_WIN if (AppConstants.platform == "win" && gUpdateMutexHandle) { // If we hold the update mutex, let it go! // The OS would clean this up sometime after shutdown, @@ -2658,6 +2702,7 @@ UpdateService.prototype = { closeHandle(gUpdateMutexHandle); gUpdateMutexHandle = null; } +#endif if (this._retryTimer) { this._retryTimer.cancel(); } @@ -2688,6 +2733,7 @@ UpdateService.prototype = { } break; case "test-close-handle-update-mutex": +#ifdef XP_WIN if (Cu.isInAutomation) { if (AppConstants.platform == "win" && gUpdateMutexHandle) { LOG("UpdateService:observe - closing mutex handle for testing"); @@ -2695,6 +2741,7 @@ UpdateService.prototype = { gUpdateMutexHandle = null; } } +#endif break; } }, @@ -2726,6 +2773,9 @@ UpdateService.prototype = { return; } gUpdateFileWriteInfo = { phase: "startup", failure: false }; +#if defined(TOR_BROWSER_UPDATE) && !defined(XP_MACOSX) + this._removeOrphanedTorBrowserFiles(); +#endif if (!this.canCheckForUpdates) { LOG( "UpdateService:_postUpdateProcessing - unable to check for " + @@ -3037,6 +3087,42 @@ UpdateService.prototype = { } },
+#if defined(TOR_BROWSER_UPDATE) && !defined(XP_MACOSX) + /** + * When updating from an earlier version to Tor Browser 6.0 or later, old + * update info files are left behind on Linux and Windows. Remove them. + */ + _removeOrphanedTorBrowserFiles: function AUS__removeOrphanedTorBrowserFiles() { + try { + let oldUpdateInfoDir = getAppBaseDir(); // aka the Browser directory. + +#ifdef XP_WIN + // On Windows, the updater files were stored under + // Browser/TorBrowser/Data/Browser/Caches/firefox/ + oldUpdateInfoDir.appendRelativePath( + "TorBrowser\Data\Browser\Caches\firefox"); +#endif + + // Remove the updates directory. + let updatesDir = oldUpdateInfoDir.clone(); + updatesDir.append("updates"); + if (updatesDir.exists() && updatesDir.isDirectory()) { + updatesDir.remove(true); + } + + // Remove files: active-update.xml and updates.xml + let filesToRemove = [ "active-update.xml", "updates.xml" ]; + filesToRemove.forEach(function(aFileName) { + let f = oldUpdateInfoDir.clone(); + f.append(aFileName); + if (f.exists()) { + f.remove(false); + } + }); + } catch (e) {} + }, +#endif + /** * Register an observer when the network comes online, so we can short-circuit * the app.update.interval when there isn't connectivity @@ -3481,9 +3567,14 @@ UpdateService.prototype = { updates.forEach(function(aUpdate) { // Ignore updates for older versions of the application and updates for // the same version of the application with the same build ID. - if ( - vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 || - (vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 && +#ifdef TOR_BROWSER_UPDATE + let compatVersion = AppConstants.TOR_BROWSER_VERSION; +#else + let compatVersion = Services.appinfo.version; +#endif + let rc = vc.compare(aUpdate.appVersion, compatVersion); + if (rc < 0 || + (rc == 0 && aUpdate.buildID == Services.appinfo.appBuildID) ) { LOG( @@ -3935,20 +4026,32 @@ UpdateService.prototype = { // build ID. If we already have an update ready, we want to apply those // same checks against the version of the ready update, so that we don't // download an update that isn't newer than the one we already have. +#ifdef TOR_BROWSER_UPDATE + let compatVersion = AppConstants.TOR_BROWSER_VERSION; +#else + let compatVersion = Services.appinfo.version; +#endif if ( updateIsAtLeastAsOldAs( update, - Services.appinfo.version, + compatVersion, Services.appinfo.appBuildID ) ) { LOG( "UpdateService:downloadUpdate - canceling download of update since " + "it is for an earlier or same application version and build ID.\n" + +#ifdef TOR_BROWSER_UPDATE + "current Tor Browser version: " + + compatVersion + + "\n" + + "update Tor Browser version : " + +#else "current application version: " + - Services.appinfo.version + + compatVersion + "\n" + "update application version : " + +#endif update.appVersion + "\n" + "current build ID: " + @@ -4628,6 +4731,7 @@ Checker.prototype = { */ _callback: null,
+#if !defined(TOR_BROWSER_UPDATE) _getCanMigrate: function UC__getCanMigrate() { if (AppConstants.platform != "win") { return false; @@ -4697,6 +4801,7 @@ Checker.prototype = { LOG("Checker:_getCanMigrate - no registry entries for this installation"); return false; }, +#endif // !defined(TOR_BROWSER_UPDATE)
/** * The URL of the update service XML file to connect to that contains details @@ -4725,9 +4830,11 @@ Checker.prototype = { url += (url.includes("?") ? "&" : "?") + "force=1"; }
+#if !defined(TOR_BROWSER_UPDATE) if (this._getCanMigrate()) { url += (url.includes("?") ? "&" : "?") + "mig64=1"; } +#endif
LOG("Checker:getUpdateURL - update URL: " + url); return url; diff --git a/toolkit/mozapps/update/UpdateServiceStub.jsm b/toolkit/mozapps/update/UpdateServiceStub.jsm index 45b9177a06cb0..ab4bbfc0884c8 100644 --- a/toolkit/mozapps/update/UpdateServiceStub.jsm +++ b/toolkit/mozapps/update/UpdateServiceStub.jsm @@ -84,8 +84,12 @@ function UpdateServiceStub() { // contains the status file's path
// We may need to migrate update data + // In Tor Browser we skip this because we do not use an update agent and we + // do not want to store any data outside of the browser installation directory. + // For more info, see https://bugzilla.mozilla.org/show_bug.cgi?id=1458314 if ( AppConstants.platform == "win" && + !AppConstants.TOR_BROWSER_UPDATE && !Services.prefs.getBoolPref(prefUpdateDirMigrated, false) ) { migrateUpdateDirectory(); diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp index b094d9eb75e9d..c825d3c1ea8e5 100644 --- a/toolkit/mozapps/update/common/updatehelper.cpp +++ b/toolkit/mozapps/update/common/updatehelper.cpp @@ -66,6 +66,13 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, * @return TRUE if successful */ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) { +#ifdef TOR_BROWSER_UPDATE + // This function is used to support the maintenance service and elevated + // updates and is therefore not called by Tor Browser's updater. We stub + // it out to avoid any chance that the Tor Browser updater will create + // files under C:\Program Files (x86)\ or a similar location. + return FALSE; +#else PWSTR progFilesX86; if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_CREATE, nullptr, &progFilesX86))) { @@ -99,6 +106,7 @@ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) { }
return TRUE; +#endif }
/** diff --git a/toolkit/mozapps/update/moz.build b/toolkit/mozapps/update/moz.build index 0ed2f39f66a08..47af63a104b31 100644 --- a/toolkit/mozapps/update/moz.build +++ b/toolkit/mozapps/update/moz.build @@ -22,7 +22,6 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [ "UpdateListener.jsm", - "UpdateService.jsm", "UpdateServiceStub.jsm", "UpdateTelemetry.jsm", ] @@ -44,6 +43,10 @@ if ( "BackgroundTask_backgroundupdate.jsm", ]
+EXTRA_PP_JS_MODULES += [ + "UpdateService.jsm", +] + XPCOM_MANIFESTS += [ "components.conf", ] diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm index 3074c0da065b0..a48318ece4c37 100644 --- a/toolkit/mozapps/update/updater/launchchild_osx.mm +++ b/toolkit/mozapps/update/updater/launchchild_osx.mm @@ -372,6 +372,7 @@ bool ObtainUpdaterArguments(int* argc, char*** argv) {
@end
+#ifndef TOR_BROWSER_UPDATE bool ServeElevatedUpdate(int argc, const char** argv) { MacAutoreleasePool pool;
@@ -387,6 +388,7 @@ bool ServeElevatedUpdate(int argc, const char** argv) { [updater release]; return didSucceed; } +#endif
bool IsOwnedByGroupAdmin(const char* aAppBundle) { MacAutoreleasePool pool; diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build index 40d7a77a6b628..ac7f82a4f9ad2 100644 --- a/toolkit/mozapps/update/updater/moz.build +++ b/toolkit/mozapps/update/updater/moz.build @@ -51,7 +51,7 @@ xpcshell_cert.script = "gen_cert_header.py:create_header" dep1_cert.script = "gen_cert_header.py:create_header" dep2_cert.script = "gen_cert_header.py:create_header"
-if CONFIG["MOZ_UPDATE_CHANNEL"] in ("beta", "release", "esr"): +if CONFIG["MOZ_UPDATE_CHANNEL"] in ("alpha", "beta", "release", "esr"): primary_cert.inputs += ["release_primary.der"] secondary_cert.inputs += ["release_secondary.der"] elif CONFIG["MOZ_UPDATE_CHANNEL"] in ( diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp index 0295d2435dd09..b9b982367137b 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -16,7 +16,7 @@ * updatev3.manifest * ----------------- * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | - * "remove" | "rmdir" | "rmrfdir" | type + * "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type * * 'add-if-not' adds a file if it doesn't exist. * @@ -78,7 +78,9 @@ bool IsRecursivelyWritable(const char* aPath); void LaunchChild(int argc, const char** argv); void LaunchMacPostProcess(const char* aAppBundle); bool ObtainUpdaterArguments(int* argc, char*** argv); +# ifndef TOR_BROWSER_UPDATE bool ServeElevatedUpdate(int argc, const char** argv); +# endif void SetGroupOwnershipAndPermissions(const char* aAppBundle); struct UpdateServerThreadArgs { int argc; @@ -483,9 +485,12 @@ static const NS_tchar* get_relative_path(const NS_tchar* fullpath) { * The line from the manifest that contains the path. * @param isdir * Whether the path is a directory path. Defaults to false. + * @param islinktarget + * Whether the path is a symbolic link target. Defaults to false. * @return valid filesystem path or nullptr if the path checks fail. */ -static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) { +static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false, + bool islinktarget = false) { NS_tchar* path = mstrtok(kQuote, line); if (!path) { LOG(("get_valid_path: unable to determine path: " LOG_S, *line)); @@ -521,10 +526,12 @@ static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) { path[NS_tstrlen(path) - 1] = NS_T('\0'); }
- // Don't allow relative paths that resolve to a parent directory. - if (NS_tstrstr(path, NS_T("..")) != nullptr) { - LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); - return nullptr; + if (!islinktarget) { + // Don't allow relative paths that resolve to a parent directory. + if (NS_tstrstr(path, NS_T("..")) != nullptr) { + LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); + return nullptr; + } }
return path; @@ -564,7 +571,7 @@ static void ensure_write_permissions(const NS_tchar* path) { (void)_wchmod(path, _S_IREAD | _S_IWRITE); #else struct stat fs; - if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) { + if (!lstat(path, &fs) && !S_ISLNK(fs.st_mode) && !(fs.st_mode & S_IWUSR)) { (void)chmod(path, fs.st_mode | S_IWUSR); } #endif @@ -751,11 +758,9 @@ static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) { return READ_ERROR; }
-# ifdef XP_UNIX if (S_ISLNK(ss.st_mode)) { return ensure_copy_symlink(path, dest); } -# endif
AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode)); if (!infile) { @@ -842,12 +847,19 @@ static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest, return READ_ERROR; }
-#ifdef XP_UNIX +#ifndef XP_WIN if (S_ISLNK(sInfo.st_mode)) { return ensure_copy_symlink(path, dest); } #endif
+#ifdef XP_UNIX + // Ignore Unix domain sockets. See #20691. + if (S_ISSOCK(sInfo.st_mode)) { + return 0; + } +#endif + if (!S_ISDIR(sInfo.st_mode)) { return ensure_copy(path, dest); } @@ -904,7 +916,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, }
struct NS_tstat_t spathInfo; - rv = NS_tstat(spath, &spathInfo); + rv = NS_tlstat(spath, &spathInfo); // Get info about file or symlink. if (rv) { LOG(("rename_file: failed to read file status info: " LOG_S ", " "err: %d", @@ -912,7 +924,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, return READ_ERROR; }
- if (!S_ISREG(spathInfo.st_mode)) { +#ifdef XP_WIN + if (!S_ISREG(spathInfo.st_mode)) +#else + if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode)) +#endif + { if (allowDirs && !S_ISDIR(spathInfo.st_mode)) { LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", spath, errno)); @@ -921,7 +938,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, LOG(("rename_file: proceeding to rename the directory")); }
- if (!NS_taccess(dpath, F_OK)) { +#ifdef XP_WIN + if (!NS_taccess(dpath, F_OK)) +#else + if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK)) +#endif + { if (ensure_remove(dpath)) { LOG( ("rename_file: destination file exists and could not be " @@ -941,7 +963,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, return OK; }
-#ifdef XP_WIN +#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE) // Remove the directory pointed to by path and all of its files and // sub-directories. If a file is in use move it to the tobedeleted directory // and attempt to schedule removal of the file on reboot @@ -1040,7 +1062,19 @@ static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) { NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), NS_T("%s") BACKUP_EXT, relPath);
- if (NS_taccess(backup, F_OK)) { + bool isLink = false; +#ifndef XP_WIN + struct stat linkInfo; + int rv = lstat(backup, &linkInfo); + if (rv) { + LOG(("backup_restore: cannot get info for backup file: " LOG_S ", err: %d", + relBackup, errno)); + return OK; + } + isLink = S_ISLNK(linkInfo.st_mode); +#endif + + if (!isLink && NS_taccess(backup, F_OK)) { LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup)); return OK; } @@ -1058,8 +1092,18 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), NS_T("%s") BACKUP_EXT, relPath);
+ bool isLink = false; +#ifndef XP_WIN + struct stat linkInfo; + int rv2 = lstat(backup, &linkInfo); + if (rv2) { + return OK; // File does not exist; nothing to do. + } + isLink = S_ISLNK(linkInfo.st_mode); +#endif + // Nothing to discard - if (NS_taccess(backup, F_OK)) { + if (!isLink && NS_taccess(backup, F_OK)) { return OK; }
@@ -1074,6 +1118,8 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { relBackup, relPath)); return WRITE_ERROR_DELETE_BACKUP; } + +# if !defined(TOR_BROWSER_UPDATE) // The MoveFileEx call to remove the file on OS reboot will fail if the // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key // but this is ok since the installer / uninstaller will delete the @@ -1090,6 +1136,7 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { "file: " LOG_S, relPath)); } +# endif } #else if (rv) { @@ -1144,7 +1191,7 @@ class Action {
class RemoveFile : public Action { public: - RemoveFile() : mSkip(0) {} + RemoveFile() : mSkip(0), mIsLink(0) {}
int Parse(NS_tchar* line) override; int Prepare() override; @@ -1155,6 +1202,7 @@ class RemoveFile : public Action { mozilla::UniquePtr<NS_tchar[]> mFile; mozilla::UniquePtr<NS_tchar[]> mRelPath; int mSkip; + int mIsLink; };
int RemoveFile::Parse(NS_tchar* line) { @@ -1177,28 +1225,39 @@ int RemoveFile::Parse(NS_tchar* line) { }
int RemoveFile::Prepare() { - // Skip the file if it already doesn't exist. - int rv = NS_taccess(mFile.get(), F_OK); - if (rv) { - mSkip = 1; - mProgressCost = 0; - return OK; + int rv; +#ifndef XP_WIN + struct stat linkInfo; + rv = lstat(mFile.get(), &linkInfo); + mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode)); +#endif + + if (!mIsLink) { + // Skip the file if it already doesn't exist. + rv = NS_taccess(mFile.get(), F_OK); + if (rv) { + mSkip = 1; + mProgressCost = 0; + return OK; + } }
LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
- // Make sure that we're actually a file... - struct NS_tstat_t fileInfo; - rv = NS_tstat(mFile.get(), &fileInfo); - if (rv) { - LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), - errno)); - return READ_ERROR; - } + if (!mIsLink) { + // Make sure that we're actually a file... + struct NS_tstat_t fileInfo; + rv = NS_tstat(mFile.get(), &fileInfo); + if (rv) { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), + errno)); + return READ_ERROR; + }
- if (!S_ISREG(fileInfo.st_mode)) { - LOG(("path present, but not a file: " LOG_S, mFile.get())); - return DELETE_ERROR_EXPECTED_FILE; + if (!S_ISREG(fileInfo.st_mode)) { + LOG(("path present, but not a file: " LOG_S, mFile.get())); + return DELETE_ERROR_EXPECTED_FILE; + } }
NS_tchar* slash = (NS_tchar*)NS_tstrrchr(mFile.get(), NS_T('/')); @@ -1227,7 +1286,13 @@ int RemoveFile::Execute() {
// The file is checked for existence here and in Prepare since it might have // been removed by a separate instruction: bug 311099. - int rv = NS_taccess(mFile.get(), F_OK); + int rv = 0; + if (mIsLink) { + struct NS_tstat_t linkInfo; + rv = NS_tlstat(mFile.get(), &linkInfo); + } else { + rv = NS_taccess(mFile.get(), F_OK); + } if (rv) { LOG(("file cannot be removed because it does not exist; skipping")); mSkip = 1; @@ -1950,6 +2015,92 @@ void PatchIfFile::Finish(int status) { PatchFile::Finish(status); }
+#ifndef XP_WIN +class AddSymlink : public Action { + public: + AddSymlink() : mAdded(false) {} + + virtual int Parse(NS_tchar* line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + + private: + mozilla::UniquePtr<NS_tchar[]> mLinkPath; + mozilla::UniquePtr<NS_tchar[]> mRelPath; + mozilla::UniquePtr<NS_tchar[]> mTarget; + bool mAdded; +}; + +int AddSymlink::Parse(NS_tchar* line) { + // format "<linkname>" "target" + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) return PARSE_ERROR; + + mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN); + NS_tstrcpy(mRelPath.get(), validPath); + mLinkPath.reset(get_full_path(validPath)); + if (!mLinkPath) { + return PARSE_ERROR; + } + + // consume whitespace between args + NS_tchar* q = mstrtok(kQuote, &line); + if (!q) return PARSE_ERROR; + + validPath = get_valid_path(&line, false, true); + if (!validPath) return PARSE_ERROR; + + mTarget = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN); + NS_tstrcpy(mTarget.get(), validPath); + + return OK; +} + +int AddSymlink::Prepare() { + LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), + mTarget.get())); + + return OK; +} + +int AddSymlink::Execute() { + LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), + mTarget.get())); + + // First make sure that we can actually get rid of any existing file or link. + struct stat linkInfo; + int rv = lstat(mLinkPath.get(), &linkInfo); + if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) { + rv = NS_taccess(mLinkPath.get(), F_OK); + } + if (rv == 0) { + rv = backup_create(mLinkPath.get()); + if (rv) return rv; + } else { + rv = ensure_parent_dir(mLinkPath.get()); + if (rv) return rv; + } + + // Create the link. + rv = symlink(mTarget.get(), mLinkPath.get()); + if (!rv) { + mAdded = true; + } + + return rv; +} + +void AddSymlink::Finish(int status) { + LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), mTarget.get())); + // When there is an update failure and a link has been added it is removed + // here since there might not be a backup to replace it. + if (status && mAdded) NS_tremove(mLinkPath.get()); + backup_finish(mLinkPath.get(), mRelPath.get(), status); +} +#endif + //-----------------------------------------------------------------------------
#ifdef XP_WIN @@ -2272,14 +2423,29 @@ static bool IsSecureUpdateStatusSucceeded(bool& isSucceeded) { */ static int CopyInstallDirToDestDir() { // These files should not be copied over to the updated app -#ifdef XP_WIN -# define SKIPLIST_COUNT 3 -#elif XP_MACOSX -# define SKIPLIST_COUNT 0 +#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef XP_WIN +# define SKIPLIST_COUNT 6 +# else +# define SKIPLIST_COUNT 5 +# endif #else -# define SKIPLIST_COUNT 2 +# ifdef XP_WIN +# define SKIPLIST_COUNT 3 +# elif XP_MACOSX +# define SKIPLIST_COUNT 0 +# else +# define SKIPLIST_COUNT 2 +# endif #endif copy_recursive_skiplist<SKIPLIST_COUNT> skiplist; +#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef XP_MACOSX + skiplist.append(0, gInstallDirPath, NS_T("Updated.app")); + skiplist.append(1, gInstallDirPath, NS_T("TorBrowser/UpdateInfo/updates/0")); +# endif +#endif + #ifndef XP_MACOSX skiplist.append(0, gInstallDirPath, NS_T("updated")); skiplist.append(1, gInstallDirPath, NS_T("updates/0")); @@ -2288,6 +2454,19 @@ static int CopyInstallDirToDestDir() { # endif #endif
+#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef XP_WIN + skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath, + NS_T("TorBrowser/Data/Browser/profile.default/parent.lock")); +# else + skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath, + NS_T("TorBrowser/Data/Browser/profile.default/.parentlock")); +# endif + + skiplist.append(SKIPLIST_COUNT - 1, gInstallDirPath, + NS_T("TorBrowser/Data/Tor/lock")); +#endif + return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist); }
@@ -2425,7 +2604,9 @@ static int ProcessReplaceRequest() { if (NS_taccess(deleteDir, F_OK)) { NS_tmkdir(deleteDir, 0755); } +# if !defined(TOR_BROWSER_UPDATE) remove_recursive_on_reboot(tmpDir, deleteDir); +# endif #endif }
@@ -2433,8 +2614,45 @@ static int ProcessReplaceRequest() { // On OS X, we we need to remove the staging directory after its Contents // directory has been moved. NS_tchar updatedAppDir[MAXPATHLEN]; +# if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]), + NS_T("%s/Updated.app"), gInstallDirPath); + // For Tor Browser on OS X, we also need to copy everything else that is + // inside Updated.app. + NS_tDIR* dir = NS_topendir(updatedAppDir); + if (dir) { + NS_tdirent* entry; + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childSrcPath[MAXPATHLEN]; + NS_tsnprintf(childSrcPath, + sizeof(childSrcPath) / sizeof(childSrcPath[0]), + NS_T("%s/%s"), updatedAppDir, entry->d_name); + NS_tchar childDstPath[MAXPATHLEN]; + NS_tsnprintf(childDstPath, + sizeof(childDstPath) / sizeof(childDstPath[0]), + NS_T("%s/%s"), gInstallDirPath, entry->d_name); + ensure_remove_recursive(childDstPath); + rv = rename_file(childSrcPath, childDstPath, true); + if (rv) { + LOG(("Moving " LOG_S " to " LOG_S " failed, err: %d", childSrcPath, + childDstPath, errno)); + } + } + } + + NS_tclosedir(dir); + } else { + LOG(("Updated.app dir can't be found: " LOG_S ", err: %d", updatedAppDir, + errno)); + } +# else NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]), NS_T("%s/Updated.app"), gPatchDirPath); +# endif + + // Remove the Updated.app directory. ensure_remove_recursive(updatedAppDir); #endif
@@ -2608,11 +2826,15 @@ static void UpdateThreadFunc(void* param) {
#ifdef XP_MACOSX static void ServeElevatedUpdateThreadFunc(void* param) { +# ifdef TOR_BROWSER_UPDATE + WriteStatusFile(ELEVATION_CANCELED); +# else UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param; gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv); if (!gSucceeded) { WriteStatusFile(ELEVATION_CANCELED); } +# endif QuitProgressUI(); }
@@ -2642,7 +2864,7 @@ int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv, #endif
if (argc > callbackIndex) { -#if defined(XP_WIN) +#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE) if (gSucceeded) { if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) { fprintf(stderr, "The post update process was not launched"); @@ -2726,8 +2948,12 @@ int NS_main(int argc, NS_tchar** argv) { mozilla::UniquePtr<UmaskContext> umaskContext(new UmaskContext(0));
bool isElevated = +# ifdef TOR_BROWSER_UPDATE + false; +# else strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0; +# endif if (isElevated) { if (!ObtainUpdaterArguments(&argc, &argv)) { // Won't actually get here because ObtainUpdaterArguments will terminate @@ -3379,6 +3605,26 @@ int NS_main(int argc, NS_tchar** argv) { // using the service is because we are testing. if (!useService && !noServiceFallback && updateLockFileHandle == INVALID_HANDLE_VALUE) { +# ifdef TOR_BROWSER_UPDATE +# ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR + // Because the TorBrowser-Data directory that contains the user's + // profile is a sibling of the Tor Browser installation directory, + // the user probably has permission to apply updates. Therefore, to + // avoid potential security issues such as CVE-2015-0833, do not + // attempt to elevate privileges. Instead, write a "failed" message + // to the update status file (this function will return immediately + // after the CloseHandle(elevatedFileHandle) call below). +# else + // Because the user profile is contained within the Tor Browser + // installation directory, the user almost certainly has permission to + // apply updates. Therefore, to avoid potential security issues such + // as CVE-2015-0833, do not attempt to elevate privileges. Instead, + // write a "failed" message to the update status file (this function + // will return immediately after the CloseHandle(elevatedFileHandle) + // call below). +# endif + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); +# else // Get the secure ID before trying to update so it is possible to // determine if the updater has created a new one. char uuidStringBefore[UUID_LEN] = {'\0'}; @@ -3424,6 +3670,7 @@ int NS_main(int argc, NS_tchar** argv) { gCopyOutputFiles = false; WriteStatusFile(ELEVATION_CANCELED); } +# endif }
// If we started the elevated updater, and it finished, check the secure @@ -3793,6 +4040,7 @@ int NS_main(int argc, NS_tchar** argv) { if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) { LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", DELETE_DIR, errno)); +# if !defined(TOR_BROWSER_UPDATE) // The directory probably couldn't be removed due to it containing files // that are in use and will be removed on OS reboot. The call to remove the // directory on OS reboot is done after the calls to remove the files so the @@ -3811,6 +4059,7 @@ int NS_main(int argc, NS_tchar** argv) { "directory: " LOG_S, DELETE_DIR)); } +# endif } #endif /* XP_WIN */
@@ -4453,7 +4702,13 @@ int DoUpdate() { action = new AddIfNotFile(); } else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists action = new PatchIfFile(); - } else { + } +#ifndef XP_WIN + else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) { + action = new AddSymlink(); + } +#endif + else { LOG(("DoUpdate: unknown token: " LOG_S, token)); free(buf); return PARSE_ERROR; diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h index f8dc75ee4d089..ce816acd83e24 100644 --- a/toolkit/xre/MacLaunchHelper.h +++ b/toolkit/xre/MacLaunchHelper.h @@ -17,7 +17,9 @@ extern "C" { * pid of the terminated process to confirm that it executed successfully. */ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0); +#ifndef TOR_BROWSER_UPDATE bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0); +#endif }
#endif diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm index ec570ffab124a..da2917c2a99e3 100644 --- a/toolkit/xre/MacLaunchHelper.mm +++ b/toolkit/xre/MacLaunchHelper.mm @@ -40,6 +40,7 @@ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid) { } }
+#ifndef TOR_BROWSER_UPDATE BOOL InstallPrivilegedHelper() { AuthorizationRef authRef = NULL; OSStatus status = AuthorizationCreate( @@ -116,3 +117,4 @@ bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid) { } return didSucceed; } +#endif diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 1b7e7bdf1e593..880d87c2be80f 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -3250,6 +3250,11 @@ static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion, gLastAppBuildID.Assign(gAppData->buildID);
nsAutoCString buf; + + nsAutoCString tbVersion(TOR_BROWSER_VERSION_QUOTED); + rv = parser.GetString("Compatibility", "LastTorBrowserVersion", buf); + if (NS_FAILED(rv) || !tbVersion.Equals(buf)) return false; + rv = parser.GetString("Compatibility", "LastOSABI", buf); if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false;
@@ -3335,6 +3340,12 @@ static void WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion, PR_Write(fd, kHeader, sizeof(kHeader) - 1); PR_Write(fd, aVersion.get(), aVersion.Length());
+ nsAutoCString tbVersion(TOR_BROWSER_VERSION_QUOTED); + static const char kTorBrowserVersionHeader[] = + NS_LINEBREAK "LastTorBrowserVersion="; + PR_Write(fd, kTorBrowserVersionHeader, sizeof(kTorBrowserVersionHeader) - 1); + PR_Write(fd, tbVersion.get(), tbVersion.Length()); + static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI="; PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1); PR_Write(fd, aOSABI.get(), aOSABI.Length()); @@ -4733,8 +4744,17 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) { if (CheckArg("test-process-updates")) { SaveToEnv("MOZ_TEST_PROCESS_UPDATES=1"); } +# ifdef TOR_BROWSER_UPDATE + nsAutoCString compatVersion(TOR_BROWSER_VERSION_QUOTED); +# endif ProcessUpdates(mDirProvider.GetGREDir(), exeDir, updRoot, gRestartArgc, - gRestartArgv, mAppData->version); + gRestartArgv, +# ifdef TOR_BROWSER_UPDATE + compatVersion.get() +# else + mAppData->version +# endif + ); if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { SaveToEnv("MOZ_TEST_PROCESS_UPDATES="); *aExitFlag = true; diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp index 09d4ed839cf95..2b176266b05ff 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -165,6 +165,13 @@ static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) { return NS_OK; }
+#ifdef DEBUG +static void dump_argv(const char* aPrefix, char** argv, int argc) { + printf("%s - %d args\n", aPrefix, argc); + for (int i = 0; i < argc; ++i) printf(" %d: %s\n", i, argv[i]); +} +#endif + static bool GetFile(nsIFile* dir, const nsACString& name, nsCOMPtr<nsIFile>& result) { nsresult rv; @@ -226,6 +233,34 @@ typedef enum { eAppliedService, } UpdateStatus;
+#ifdef DEBUG +static const char* UpdateStatusToString(UpdateStatus aStatus) { + const char* rv = "unknown"; + switch (aStatus) { + case eNoUpdateAction: + rv = "NoUpdateAction"; + break; + case ePendingUpdate: + rv = "PendingUpdate"; + break; + case ePendingService: + rv = "PendingService"; + break; + case ePendingElevate: + rv = "PendingElevate"; + break; + case eAppliedUpdate: + rv = "AppliedUpdate"; + break; + case eAppliedService: + rv = "AppliedService"; + break; + } + + return rv; +} +#endif + /** * Returns a value indicating what needs to be done in order to handle an * update. @@ -298,9 +333,39 @@ static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) { return false; }
+#ifdef DEBUG + printf("IsOlderVersion checking appVersion %s against updateVersion %s\n", + appVersion, buf); +#endif + return mozilla::Version(appVersion) > buf; }
+#ifndef TOR_BROWSER_DATA_OUTSIDE_APP_DIR +# if defined(TOR_BROWSER_UPDATE) && defined(XP_MACOSX) +static nsresult GetUpdateDirFromAppDir(nsIFile* aAppDir, nsIFile** aResult) { + // On Mac OSX, we stage the update to an Updated.app directory that is + // directly below the main Tor Browser.app directory (two levels up from + // the appDir). + NS_ENSURE_ARG_POINTER(aAppDir); + NS_ENSURE_ARG_POINTER(aResult); + nsCOMPtr<nsIFile> parentDir1, parentDir2; + nsresult rv = aAppDir->GetParent(getter_AddRefs(parentDir1)); + NS_ENSURE_SUCCESS(rv, rv); + rv = parentDir1->GetParent(getter_AddRefs(parentDir2)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> updatedDir; + if (!GetFile(parentDir2, "Updated.app"_ns, updatedDir)) { + return NS_ERROR_FAILURE; + } + + updatedDir.forget(aResult); + return NS_OK; +} +# endif +#endif + /** * Applies, switches, or stages an update. * @@ -448,7 +513,12 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } else { // Get the directory where the update is staged or will be staged. #if defined(XP_MACOSX) +# if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + rv = GetUpdateDirFromAppDir(appDir, getter_AddRefs(updatedDir)); + if (NS_FAILED(rv)) { +# else if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) { +# endif #else if (!GetFile(appDir, "updated"_ns, updatedDir)) { #endif @@ -543,6 +613,9 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, }
LOG(("spawning updater process [%s]\n", updaterPath.get())); +#ifdef DEBUG + dump_argv("ApplyUpdate updater", argv, argc); +#endif
#if defined(XP_UNIX) && !defined(XP_MACOSX) // We use execv to spawn the updater process on all UNIX systems except Mac @@ -581,6 +654,10 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } #elif defined(XP_MACOSX) UpdateDriverSetupMacCommandLine(argc, argv, restart); +# ifdef DEBUG +dump_argv("ApplyUpdate after SetupMacCommandLine", argv, argc); +# endif +# ifndef TOR_BROWSER_UPDATE // We need to detect whether elevation is required for this update. This can // occur when an admin user installs the application, but another admin // user attempts to update (see bug 394984). @@ -593,6 +670,7 @@ if (restart && !IsRecursivelyWritable(installDirPath.get())) { } exit(0); } +# endif
if (isStaged) { // Launch the updater to replace the installation with the staged updated. @@ -663,9 +741,27 @@ static bool ProcessHasTerminated(ProcessType pt) { nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, int argc, char** argv, const char* appVersion, bool restart, ProcessType* pid) { +#if defined(XP_WIN) && defined(TOR_BROWSER_UPDATE) + // Try to remove the "tobedeleted" directory which, if present, contains + // files that could not be removed during a previous update (e.g., DLLs + // that were in use and therefore locked by Windows). + nsCOMPtr<nsIFile> deleteDir; + nsresult winrv = appDir->Clone(getter_AddRefs(deleteDir)); + if (NS_SUCCEEDED(winrv)) { + winrv = deleteDir->AppendNative("tobedeleted"_ns); + if (NS_SUCCEEDED(winrv)) { + winrv = deleteDir->Remove(true); + } + } +#endif + nsresult rv;
nsCOMPtr<nsIFile> updatesDir; +#ifdef DEBUG + printf("ProcessUpdates updateRootDir: %s appVersion: %s\n", + updRootDir->HumanReadablePath().get(), appVersion); +#endif rv = updRootDir->Clone(getter_AddRefs(updatesDir)); NS_ENSURE_SUCCESS(rv, rv); rv = updatesDir->AppendNative("updates"_ns); @@ -685,6 +781,12 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
nsCOMPtr<nsIFile> statusFile; UpdateStatus status = GetUpdateStatus(updatesDir, statusFile); +#ifdef DEBUG + printf("ProcessUpdates status: %s (%d)\n", UpdateStatusToString(status), + status); + printf("ProcessUpdates updatesDir: %s\n", + updatesDir->HumanReadablePath().get()); +#endif switch (status) { case ePendingUpdate: case ePendingService: { @@ -748,13 +850,16 @@ nsUpdateProcessor::ProcessUpdate() { NS_ENSURE_SUCCESS(rv, rv); }
+ nsAutoCString appVersion; +#ifdef TOR_BROWSER_UPDATE + appVersion = TOR_BROWSER_VERSION_QUOTED; +#else nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1", &rv); NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString appVersion; rv = appInfo->GetVersion(appVersion); NS_ENSURE_SUCCESS(rv, rv); +#endif
// Copy the parameters to the StagedUpdateInfo structure shared with the // watcher thread. diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp index 85f84278d886b..7023ab09ef2b1 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -1231,6 +1231,37 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, } #endif nsCOMPtr<nsIFile> updRoot; +#if defined(TOR_BROWSER_UPDATE) + // For Tor Browser, we store update history, etc. within the UpdateInfo + // directory under the user data directory. + nsresult rv = GetTorBrowserUserDataDir(getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + rv = updRoot->AppendNative("UpdateInfo"_ns); + NS_ENSURE_SUCCESS(rv, rv); +# if defined(XP_MACOSX) && defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + // Since the TorBrowser-Data directory may be shared among different + // installations of the application, embed the app path in the update dir + // so that the update history is partitioned. This is much less likely to + // be an issue on Linux or Windows because the Tor Browser packages for + // those platforms include a "container" folder that provides partitioning + // by default, and we do not support use of a shared, OS-recommended area + // for user data on those platforms. + nsAutoString appDirPath; + nsCOMPtr<nsIFile> appRootDir; + rv = GetAppRootDir(getter_AddRefs(appRootDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = appRootDir->GetPath(appDirPath); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t dotIndex = appDirPath.RFind(".app"); + if (dotIndex == kNotFound) { + dotIndex = appDirPath.Length(); + } + appDirPath = Substring(appDirPath, 1, dotIndex - 1); + rv = updRoot->AppendRelativePath(appDirPath); + NS_ENSURE_SUCCESS(rv, rv); +# endif +#else // ! TOR_BROWSER_UPDATE nsCOMPtr<nsIFile> appFile; bool per = false; nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); @@ -1238,7 +1269,7 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, rv = appFile->GetParent(getter_AddRefs(updRoot)); NS_ENSURE_SUCCESS(rv, rv);
-#ifdef XP_MACOSX +# ifdef XP_MACOSX nsCOMPtr<nsIFile> appRootDirFile; nsCOMPtr<nsIFile> localDir; nsAutoString appDirPath; @@ -1272,7 +1303,7 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, localDir.forget(aResult); return NS_OK;
-#elif XP_WIN +# elif XP_WIN nsAutoString installPath; rv = updRoot->GetPath(installPath); NS_ENSURE_SUCCESS(rv, rv); @@ -1301,7 +1332,8 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, nsAutoString updatePathStr; updatePathStr.Assign(updatePath.get()); updRoot->InitWithPath(updatePathStr); -#endif // XP_WIN +# endif // XP_WIN +#endif // ! TOR_BROWSER_UPDATE updRoot.forget(aResult); return NS_OK; } diff --git a/tools/update-packaging/common.sh b/tools/update-packaging/common.sh index 4b994f30169c9..26eabbf313791 100755 --- a/tools/update-packaging/common.sh +++ b/tools/update-packaging/common.sh @@ -8,6 +8,10 @@ # Author: Darin Fisher #
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove all lines in this file that contain: +# TorBrowser/Data + # ----------------------------------------------------------------------------- QUIET=0
@@ -76,17 +80,8 @@ make_add_instruction() { forced= fi
- is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/') - if [ $is_extension = "1" ]; then - # Use the subdirectory of the extensions folder as the file to test - # before performing this add instruction. - testdir=$(echo "$f" | sed 's/(.*distribution/extensions/[^/]*)/.*/\1/') - verbose_notice " add-if "$testdir" "$f"" - echo "add-if "$testdir" "$f"" >> "$filev3" - else - verbose_notice " add "$f"$forced" - echo "add "$f"" >> "$filev3" - fi + verbose_notice " add "$f"$forced" + echo "add "$f"" >> "$filev3" }
check_for_add_if_not_update() { @@ -109,21 +104,21 @@ make_add_if_not_instruction() { echo "add-if-not "$f" "$f"" >> "$filev3" }
+make_addsymlink_instruction() { + link="$1" + target="$2" + filev3="$3" + + verbose_notice " addsymlink: $link -> $target" + echo "addsymlink "$link" "$target"" >> "$filev3" +} + make_patch_instruction() { f="$1" filev3="$2"
- is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/') - if [ $is_extension = "1" ]; then - # Use the subdirectory of the extensions folder as the file to test - # before performing this add instruction. - testdir=$(echo "$f" | sed 's/(.*distribution/extensions/[^/]*)/.*/\1/') - verbose_notice " patch-if "$testdir" "$f.patch" "$f"" - echo "patch-if "$testdir" "$f.patch" "$f"" >> "$filev3" - else - verbose_notice " patch "$f.patch" "$f"" - echo "patch "$f.patch" "$f"" >> "$filev3" - fi + verbose_notice " patch "$f.patch" "$f"" + echo "patch "$f.patch" "$f"" >> "$filev3" }
append_remove_instructions() { @@ -168,6 +163,10 @@ append_remove_instructions() {
# List all files in the current directory, stripping leading "./" # Pass a variable name and it will be filled as an array. +# To support Tor Browser updates, skip the following files: +# TorBrowser/Data/Browser/profiles.ini +# TorBrowser/Data/Browser/profile.default/bookmarks.html +# TorBrowser/Data/Tor/torrc list_files() { count=0 temp_filelist=$(mktemp) @@ -178,6 +177,11 @@ list_files() { | sed 's/./(.*)/\1/' \ | sort -r > "${temp_filelist}" while read file; do + if [ "$file" = "TorBrowser/Data/Browser/profiles.ini" -o \ + "$file" = "TorBrowser/Data/Browser/profile.default/bookmarks.html" -o \ + "$file" = "TorBrowser/Data/Tor/torrc" ]; then + continue; + fi eval "${1}[$count]="$file"" (( count++ )) done < "${temp_filelist}" @@ -199,3 +203,19 @@ list_dirs() { done < "${temp_dirlist}" rm "${temp_dirlist}" } + +# List all symbolic links in the current directory, stripping leading "./" +list_symlinks() { + count=0 + + find . -type l \ + | sed 's/./(.*)/\1/' \ + | sort -r > "temp-symlinklist" + while read symlink; do + target=$(readlink "$symlink") + eval "${1}[$count]="$symlink"" + eval "${2}[$count]="$target"" + (( count++ )) + done < "temp-symlinklist" + rm "temp-symlinklist" +} diff --git a/tools/update-packaging/make_full_update.sh b/tools/update-packaging/make_full_update.sh index db2c5898efdc2..603988997405d 100755 --- a/tools/update-packaging/make_full_update.sh +++ b/tools/update-packaging/make_full_update.sh @@ -71,6 +71,7 @@ if [ ! -f "precomplete" ]; then fi
list_files files +list_symlinks symlinks symlink_targets
popd
@@ -81,6 +82,21 @@ notice "Adding type instruction to update manifests" notice " type complete" echo "type "complete"" >> "$updatemanifestv3"
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +# If removal of any old, existing directories is desired, emit the appropriate +# rmrfdir commands. +notice "" +notice "Adding directory removal instructions to update manifests" +for dir_to_remove in $directories_to_remove; do + # rmrfdir requires a trailing slash; if slash is missing, add one. + if ! [[ "$dir_to_remove" =~ /$ ]]; then + dir_to_remove="${dir_to_remove}/" + fi + echo "rmrfdir "$dir_to_remove"" >> "$updatemanifestv3" +done +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + notice "" notice "Adding file add instructions to update manifests" num_files=${#files[*]} @@ -102,6 +118,15 @@ for ((i=0; $i<$num_files; i=$i+1)); do targetfiles="$targetfiles "$f"" done
+notice "" +notice "Adding symlink add instructions to update manifests" +num_symlinks=${#symlinks[*]} +for ((i=0; $i<$num_symlinks; i=$i+1)); do + link="${symlinks[$i]}" + target="${symlink_targets[$i]}" + make_addsymlink_instruction "$link" "$target" "$updatemanifestv3" +done + # Append remove instructions for any dead files. notice "" notice "Adding file and directory remove instructions from file 'removed-files'" diff --git a/tools/update-packaging/make_incremental_update.sh b/tools/update-packaging/make_incremental_update.sh index 24d68616731af..1adfef8fd96e1 100755 --- a/tools/update-packaging/make_incremental_update.sh +++ b/tools/update-packaging/make_incremental_update.sh @@ -78,7 +78,11 @@ if [ $# = 0 ]; then exit 1 fi
-requested_forced_updates='Contents/MacOS/firefox' +# Firefox uses requested_forced_updates='Contents/MacOS/firefox' due to +# 770996 but in Tor Browser we do not need that fix. +requested_forced_updates="" +directories_to_remove="" +extra_files_to_remove=""
while getopts "hqf:" flag do @@ -114,6 +118,28 @@ workdir="$(mktemp -d)" updatemanifestv3="$workdir/updatev3.manifest" archivefiles="updatev3.manifest"
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +# If the NoScript extension has changed between +# releases, add it to the "force updates" list. +ext_path='TorBrowser/Data/Browser/profile.default/extensions' +if [ -d "$newdir/$ext_path" ]; then + noscript='{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi' + + # NoScript is a packed extension, so we simply compare the old and the new + # .xpi files. + noscript_path="$ext_path/$noscript" + diff -a "$olddir/$noscript_path" "$newdir/$noscript_path" > /dev/null + rc=$? + if [ $rc -gt 1 ]; then + notice "Unexpected exit $rc from $noscript_path diff command" + exit 2 + elif [ $rc -eq 1 ]; then + requested_forced_updates="$requested_forced_updates $noscript_path" + fi +fi +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + mkdir -p "$workdir"
# Generate a list of all files in the target directory. @@ -124,6 +150,7 @@ fi
list_files oldfiles list_dirs olddirs +list_symlinks oldsymlinks oldsymlink_targets
popd
@@ -141,6 +168,7 @@ fi
list_dirs newdirs list_files newfiles +list_symlinks newsymlinks newsymlink_targets
popd
@@ -151,6 +179,22 @@ notice "Adding type instruction to update manifests" notice " type partial" echo "type "partial"" >> $updatemanifestv3
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +# If removal of any old, existing directories is desired, emit the appropriate +# rmrfdir commands. +notice "" +notice "Adding directory removal instructions to update manifests" +for dir_to_remove in $directories_to_remove; do + # rmrfdir requires a trailing slash, so add one if missing. + if ! [[ "$dir_to_remove" =~ /$ ]]; then + dir_to_remove="${dir_to_remove}/" + fi + echo "rmrfdir "$dir_to_remove"" >> "$updatemanifestv3" +done +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + + notice "" notice "Adding file patch and add instructions to update manifests"
@@ -242,6 +286,23 @@ for ((i=0; $i<$num_oldfiles; i=$i+1)); do fi done
+# Remove and re-add symlinks +notice "" +notice "Adding symlink remove/add instructions to update manifests" +num_oldsymlinks=${#oldsymlinks[*]} +for ((i=0; $i<$num_oldsymlinks; i=$i+1)); do + link="${oldsymlinks[$i]}" + verbose_notice " remove: $link" + echo "remove "$link"" >> "$updatemanifestv3" +done + +num_newsymlinks=${#newsymlinks[*]} +for ((i=0; $i<$num_newsymlinks; i=$i+1)); do + link="${newsymlinks[$i]}" + target="${newsymlink_targets[$i]}" + make_addsymlink_instruction "$link" "$target" "$updatemanifestv3" +done + # Newly added files notice "" notice "Adding file add instructions to update manifests" @@ -286,6 +347,14 @@ notice "" notice "Adding file and directory remove instructions from file 'removed-files'" append_remove_instructions "$newdir" "$updatemanifestv3"
+# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +for f in $extra_files_to_remove; do + notice " remove "$f"" + echo "remove "$f"" >> "$updatemanifestv3" +done +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + notice "" notice "Adding directory remove instructions for directories that no longer exist" num_olddirs=${#olddirs[*]}
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 1414610f0ef0e7e41755b053a7210bb24e2a81c1 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Wed Dec 17 16:37:11 2014 -0500
Bug 13379: Sign our MAR files.
Configure with --enable-verify-mar (when updating, require a valid signature on the MAR file before it is applied). Use the Tor Browser version instead of the Firefox version inside the MAR file info block (necessary to prevent downgrade attacks). Use NSS on all platforms for checking MAR signatures (instead of using OS-native APIs, which Mozilla does on Mac OS and Windows). So that the NSS and NSPR libraries the updater depends on can be found at runtime, we add the firefox directory to the shared library search path on macOS. On Linux, rpath is used by Mozilla to solve that problem, but that approach won't work on macOS because the updater executable is copied during the update process to a location that is under TorBrowser-Data, and the location of TorBrowser-Data varies.
Also includes the fix for bug 18900.
Bug 19121: reinstate the update.xml hash check
Revert most changes from Mozilla Bug 1373267 "Remove hashFunction and hashValue attributes from nsIUpdatePatch and code related to these attributes." Changes to the tests were not reverted; the tests have been changed significantly and we do not run automated updater tests for Tor Browser at this time.
Also partial revert of commit f1241db6986e4b54473a1ed870f7584c75d51122.
Revert the nsUpdateService.js changes from Mozilla Bug 862173 "don't verify mar file hash when using mar signing to verify the mar file (lessens main thread I/O)."
Changes to the tests were not reverted; the tests have been changed significantly and we do not run automated updater tests for Tor Browser at this time.
We kept the addition to the AppConstants API in case other JS code references it in the future. --- browser/config/mozconfigs/tor-browser | 1 + modules/libmar/tool/mar.c | 6 +-- modules/libmar/tool/moz.build | 12 +++-- modules/libmar/verify/moz.build | 14 ++--- toolkit/modules/AppConstants.jsm | 7 +++ toolkit/mozapps/update/UpdateService.jsm | 63 +++++++++++++++++++++- toolkit/mozapps/update/UpdateTelemetry.jsm | 1 + toolkit/mozapps/update/nsIUpdateService.idl | 11 ++++ .../mozapps/update/updater/updater-common.build | 24 +++++++-- toolkit/mozapps/update/updater/updater.cpp | 25 +++++---- toolkit/xre/moz.build | 3 ++ toolkit/xre/nsUpdateDriver.cpp | 50 +++++++++++++++++ 12 files changed, 191 insertions(+), 26 deletions(-)
diff --git a/browser/config/mozconfigs/tor-browser b/browser/config/mozconfigs/tor-browser index 8969a88aeaf9c..0c10a3334cc20 100644 --- a/browser/config/mozconfigs/tor-browser +++ b/browser/config/mozconfigs/tor-browser @@ -5,5 +5,6 @@ mk_add_options MOZ_APP_DISPLAYNAME="Tor Browser" ac_add_options --with-relative-profile=TorBrowser/Data/Browser
ac_add_options --enable-tor-browser-update +ac_add_options --enable-verify-mar
ac_add_options --with-distribution-id=org.torproject diff --git a/modules/libmar/tool/mar.c b/modules/libmar/tool/mar.c index 0bf2cb4bd1d4c..ea2b79924914b 100644 --- a/modules/libmar/tool/mar.c +++ b/modules/libmar/tool/mar.c @@ -65,7 +65,7 @@ static void print_usage() { "signed_input_archive.mar base_64_encoded_signature_file " "changed_signed_output.mar\n"); printf("(i) is the index of the certificate to extract\n"); -# if defined(XP_MACOSX) || (defined(XP_WIN) && !defined(MAR_NSS)) +# if (defined(XP_MACOSX) || defined(XP_WIN)) && !defined(MAR_NSS) printf("Verify a MAR file:\n"); printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n"); printf( @@ -149,7 +149,7 @@ int main(int argc, char** argv) { memset((void*)certBuffers, 0, sizeof(certBuffers)); #endif #if !defined(NO_SIGN_VERIFY) && \ - ((!defined(MAR_NSS) && defined(XP_WIN)) || defined(XP_MACOSX)) + (!defined(MAR_NSS) && (defined(XP_WIN) || defined(XP_MACOSX))) memset(DERFilePaths, 0, sizeof(DERFilePaths)); memset(fileSizes, 0, sizeof(fileSizes)); #endif @@ -181,7 +181,7 @@ int main(int argc, char** argv) { argc -= 2; } #if !defined(NO_SIGN_VERIFY) -# if (!defined(MAR_NSS) && defined(XP_WIN)) || defined(XP_MACOSX) +# if (!defined(MAR_NSS) && (defined(XP_WIN) || defined(XP_MACOSX))) /* -D DERFilePath, also matches -D[index] DERFilePath We allow an index for verifying to be symmetric with the import and export command line arguments. */ diff --git a/modules/libmar/tool/moz.build b/modules/libmar/tool/moz.build index a6d26c66a668e..d6fa1677ddf16 100644 --- a/modules/libmar/tool/moz.build +++ b/modules/libmar/tool/moz.build @@ -43,15 +43,21 @@ if CONFIG["MOZ_BUILD_APP"] != "tools/update-packaging": "verifymar", ]
+ if CONFIG["TOR_BROWSER_UPDATE"]: + DEFINES["MAR_NSS"] = True + if CONFIG["OS_ARCH"] == "WINNT": USE_STATIC_LIBS = True
OS_LIBS += [ "ws2_32", - "crypt32", - "advapi32", ] - elif CONFIG["OS_ARCH"] == "Darwin": + if not CONFIG["TOR_BROWSER_UPDATE"]: + OS_LIBS += [ + "crypt32", + "advapi32", + ] + elif CONFIG["OS_ARCH"] == "Darwin" and not CONFIG["TOR_BROWSER_UPDATE"]: OS_LIBS += [ "-framework Security", ] diff --git a/modules/libmar/verify/moz.build b/modules/libmar/verify/moz.build index b07475655f0dc..03718eee50b47 100644 --- a/modules/libmar/verify/moz.build +++ b/modules/libmar/verify/moz.build @@ -16,15 +16,12 @@ FORCE_STATIC_LIB = True if CONFIG["OS_ARCH"] == "WINNT": USE_STATIC_LIBS = True elif CONFIG["OS_ARCH"] == "Darwin": - UNIFIED_SOURCES += [ - "MacVerifyCrypto.cpp", - ] - OS_LIBS += [ - "-framework Security", + USE_LIBS += [ + "nspr", + "nss", + "signmar", ] else: - DEFINES["MAR_NSS"] = True - LOCAL_INCLUDES += ["../sign"] USE_LIBS += [ "nspr", "nss", @@ -38,6 +35,9 @@ else: "-Wl,-rpath=\$$ORIGIN", ]
+DEFINES["MAR_NSS"] = True +LOCAL_INCLUDES += ["../sign"] + LOCAL_INCLUDES += [ "../src", ] diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm index ea10dc97535d2..3cb1518f2ab32 100644 --- a/toolkit/modules/AppConstants.jsm +++ b/toolkit/modules/AppConstants.jsm @@ -212,6 +212,13 @@ this.AppConstants = Object.freeze({ false, #endif
+ MOZ_VERIFY_MAR_SIGNATURE: +#ifdef MOZ_VERIFY_MAR_SIGNATURE + true, +#else + false, +#endif + MOZ_MAINTENANCE_SERVICE: #ifdef MOZ_MAINTENANCE_SERVICE true, diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm index 8552240a1df67..f0a48d0216386 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -999,6 +999,20 @@ function LOG(string) { } }
+/** + * Convert a string containing binary values to hex. + */ +function binaryToHex(input) { + var result = ""; + for (var i = 0; i < input.length; ++i) { + var hex = input.charCodeAt(i).toString(16); + if (hex.length == 1) + hex = "0" + hex; + result += hex; + } + return result; +} + /** * Gets the specified directory at the specified hierarchy under the * update root directory and creates it if it doesn't exist. @@ -2022,6 +2036,8 @@ function UpdatePatch(patch) { } break; case "finalURL": + case "hashFunction": + case "hashValue": case "state": case "type": case "URL": @@ -2041,6 +2057,8 @@ UpdatePatch.prototype = { // over writing nsIUpdatePatch attributes. _attrNames: [ "errorCode", + "hashFunction", + "hashValue", "finalURL", "selected", "size", @@ -2054,6 +2072,8 @@ UpdatePatch.prototype = { */ serialize: function UpdatePatch_serialize(updates) { var patch = updates.createElementNS(URI_UPDATE_NS, "patch"); + patch.setAttribute("hashFunction", this.hashFunction); + patch.setAttribute("hashValue", this.hashValue); patch.setAttribute("size", this.size); patch.setAttribute("type", this.type); patch.setAttribute("URL", this.URL); @@ -5278,7 +5298,42 @@ Downloader.prototype = { }
LOG("Downloader:_verifyDownload downloaded size == expected size."); - return true; + let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + + let digest; + try { + let hash = Cc["@mozilla.org/security/hash;1"]. + createInstance(Ci.nsICryptoHash); + var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()]; + if (hashFunction == undefined) { + throw Cr.NS_ERROR_UNEXPECTED; + } + hash.init(hashFunction); + hash.updateFromStream(fileStream, -1); + // NOTE: For now, we assume that the format of _patch.hashValue is hex + // encoded binary (such as what is typically output by programs like + // sha1sum). In the future, this may change to base64 depending on how + // we choose to compute these hashes. + digest = binaryToHex(hash.finish(false)); + } catch (e) { + LOG("Downloader:_verifyDownload - failed to compute hash of the " + + "downloaded update archive"); + digest = ""; + } + + fileStream.close(); + + if (digest == this._patch.hashValue.toLowerCase()) { + LOG("Downloader:_verifyDownload hashes match."); + return true; + } + + LOG("Downloader:_verifyDownload hashes do not match. "); + AUSTLMY.pingDownloadCode(this.isCompleteUpdate, + AUSTLMY.DWNLD_ERR_VERIFY_NO_HASH_MATCH); + return false; },
/** @@ -5875,6 +5930,9 @@ Downloader.prototype = { " is higher than patch size: " + this._patch.size ); + // It's important that we use a different code than + // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference + // between a hash error and a wrong download error. AUSTLMY.pingDownloadCode( this.isCompleteUpdate, AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER @@ -5893,6 +5951,9 @@ Downloader.prototype = { " is not equal to expected patch size: " + this._patch.size ); + // It's important that we use a different code than + // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference + // between a hash error and a wrong download error. AUSTLMY.pingDownloadCode( this.isCompleteUpdate, AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL diff --git a/toolkit/mozapps/update/UpdateTelemetry.jsm b/toolkit/mozapps/update/UpdateTelemetry.jsm index dae76e09acd05..df5b8917970e7 100644 --- a/toolkit/mozapps/update/UpdateTelemetry.jsm +++ b/toolkit/mozapps/update/UpdateTelemetry.jsm @@ -192,6 +192,7 @@ var AUSTLMY = { DWNLD_ERR_VERIFY_NO_REQUEST: 13, DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14, DWNLD_ERR_WRITE_FAILURE: 15, + DWNLD_ERR_VERIFY_NO_HASH_MATCH: 16, // Temporary failure code to see if there are failures without an update phase DWNLD_UNKNOWN_PHASE_ERR_WRITE_FAILURE: 40,
diff --git a/toolkit/mozapps/update/nsIUpdateService.idl b/toolkit/mozapps/update/nsIUpdateService.idl index e0a075cfd1265..e3b0252b527a8 100644 --- a/toolkit/mozapps/update/nsIUpdateService.idl +++ b/toolkit/mozapps/update/nsIUpdateService.idl @@ -39,6 +39,17 @@ interface nsIUpdatePatch : nsISupports */ attribute AString finalURL;
+ /** + * The hash function to use when determining this file's integrity + */ + attribute AString hashFunction; + + /** + * The value of the hash function named above that should be computed if + * this file is not corrupt. + */ + attribute AString hashValue; + /** * The size of this file, in bytes. */ diff --git a/toolkit/mozapps/update/updater/updater-common.build b/toolkit/mozapps/update/updater/updater-common.build index 13926ea820468..a4173889271bc 100644 --- a/toolkit/mozapps/update/updater/updater-common.build +++ b/toolkit/mozapps/update/updater/updater-common.build @@ -4,6 +4,10 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+DEFINES["MAR_NSS"] = True + +link_with_nss = DEFINES["MAR_NSS"] or (CONFIG["OS_ARCH"] == "Linux" and CONFIG["MOZ_VERIFY_MAR_SIGNATURE"]) + srcs = [ "archivereader.cpp", "updater.cpp", @@ -36,10 +40,14 @@ if CONFIG["OS_ARCH"] == "WINNT": "ws2_32", "shell32", "shlwapi", - "crypt32", - "advapi32", ]
+ if not link_with_nss: + OS_LIBS += [ + "crypt32", + "advapi32", + ] + USE_LIBS += [ "bspatch", "mar", @@ -47,6 +55,13 @@ USE_LIBS += [ "xz-embedded", ]
+if link_with_nss: + USE_LIBS += [ + "nspr", + "nss", + "signmar", + ] + if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": have_progressui = 1 srcs += [ @@ -61,9 +76,12 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": ] OS_LIBS += [ "-framework Cocoa", - "-framework Security", "-framework SystemConfiguration", ] + if not link_with_nss: + OS_LIBS += [ + "-framework Security", + ] UNIFIED_SOURCES += [ "/toolkit/xre/updaterfileutils_osx.mm", ] diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp index b9b982367137b..f1598ea9b529e 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -110,9 +110,11 @@ struct UpdateServerThreadArgs { # define stat64 stat #endif
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX) -# include "nss.h" -# include "prerror.h" +#if defined(MOZ_VERIFY_MAR_SIGNATURE) +# if defined(MAR_NSS) || (!defined(XP_WIN) && !defined(XP_MACOSX)) +# include "nss.h" +# include "prerror.h" +# endif #endif
#include "crctable.h" @@ -2726,8 +2728,13 @@ static void UpdateThreadFunc(void* param) { if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) { rv = UPDATE_SETTINGS_FILE_CHANNEL; } else { +# ifdef TOR_BROWSER_UPDATE + const char* appVersion = TOR_BROWSER_VERSION_QUOTED; +# else + const char* appVersion = MOZ_APP_VERSION; +# endif rv = gArchiveReader.VerifyProductInformation( - MARStrings.MARChannelID.get(), MOZ_APP_VERSION); + MARStrings.MARChannelID.get(), appVersion); } } } @@ -2963,11 +2970,10 @@ int NS_main(int argc, NS_tchar** argv) { } #endif
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX) - // On Windows and Mac we rely on native APIs to do verifications so we don't - // need to initialize NSS at all there. - // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS - // databases. +#if defined(MOZ_VERIFY_MAR_SIGNATURE) +# if defined(MAR_NSS) || (!defined(XP_WIN) && !defined(XP_MACOSX)) + // If using NSS for signature verification, initialize NSS but minimize + // the portion we depend on by avoiding all of the NSS databases. if (NSS_NoDB_Init(nullptr) != SECSuccess) { PRErrorCode error = PR_GetError(); fprintf(stderr, "Could not initialize NSS: %s (%d)", PR_ErrorToName(error), @@ -2975,6 +2981,7 @@ int NS_main(int argc, NS_tchar** argv) { _exit(1); } #endif +#endif
#ifdef XP_MACOSX if (!isElevated) { diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build index 90d06481ee9e3..56a2d7173d3c9 100644 --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build @@ -233,6 +233,9 @@ for var in ("APP_VERSION", "APP_ID"): if CONFIG["MOZ_BUILD_APP"] == "browser": DEFINES["MOZ_BUILD_APP_IS_BROWSER"] = True
+if CONFIG['TOR_BROWSER_UPDATE']: + DEFINES['MAR_NSS'] = True + LOCAL_INCLUDES += [ "../../other-licenses/nsis/Contrib/CityHash/cityhash", "../components/find", diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp index 2b176266b05ff..c230567bd0139 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -366,6 +366,42 @@ static nsresult GetUpdateDirFromAppDir(nsIFile* aAppDir, nsIFile** aResult) { # endif #endif
+#if defined(TOR_BROWSER_UPDATE) && defined(MOZ_VERIFY_MAR_SIGNATURE) && \ + defined(MAR_NSS) && defined(XP_MACOSX) +/** + * Ideally we would save and restore the original library path value after + * the updater finishes its work (and before firefox is re-launched). + * Doing so would avoid potential problems like the following bug: + * https://bugzilla.mozilla.org/show_bug.cgi?id=1434033 + */ +/** + * Appends the specified path to the library path. + * This is used so that the updater can find libnss3.dylib and other + * shared libs. + * + * @param pathToAppend A new library path to prepend to the dynamic linker's + * search path. + */ +# include "prprf.h" +# define PATH_SEPARATOR ":" +# define LD_LIBRARY_PATH_ENVVAR_NAME "DYLD_LIBRARY_PATH" +static void AppendToLibPath(const char* pathToAppend) { + char* pathValue = getenv(LD_LIBRARY_PATH_ENVVAR_NAME); + if (nullptr == pathValue || '\0' == *pathValue) { + // Leak the string because that is required by PR_SetEnv. + char* s = + Smprintf("%s=%s", LD_LIBRARY_PATH_ENVVAR_NAME, pathToAppend).release(); + PR_SetEnv(s); + } else { + // Leak the string because that is required by PR_SetEnv. + char* s = Smprintf("%s=%s" PATH_SEPARATOR "%s", LD_LIBRARY_PATH_ENVVAR_NAME, + pathToAppend, pathValue) + .release(); + PR_SetEnv(s); + } +} +#endif + /** * Applies, switches, or stages an update. * @@ -612,6 +648,20 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); }
+#if defined(TOR_BROWSER_UPDATE) && defined(MOZ_VERIFY_MAR_SIGNATURE) && \ + defined(MAR_NSS) && defined(XP_MACOSX) + // On macOS, append the app directory to the shared library search path + // so the system can locate the shared libraries that are needed by the + // updater, e.g., libnss3.dylib). + nsAutoCString appPath; + nsresult rv2 = appDir->GetNativePath(appPath); + if (NS_SUCCEEDED(rv2)) { + AppendToLibPath(appPath.get()); + } else { + LOG(("ApplyUpdate -- appDir->GetNativePath() failed (0x%x)\n", rv2)); + } +#endif + LOG(("spawning updater process [%s]\n", updaterPath.get())); #ifdef DEBUG dump_argv("ApplyUpdate updater", argv, argc);
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 03e2234b71de721a9c962e613f7ade0793a288c3 Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Wed Nov 25 11:36:20 2015 -0500
Bug 16940: After update, load local change notes.
Add an about:tbupdate page that displays the first section from TorBrowser/Docs/ChangeLog.txt and includes a link to the remote post-update page (typically our blog entry for the release).
Always load about:tbupdate in a content process, but implement the code that reads the file system (changelog) in the chrome process for compatibility with future sandboxing efforts.
Also fix bug 29440. Now about:tbupdate is styled as a fairly simple changelog page that is designed to be displayed via a link that is on about:tor. --- browser/actors/AboutTBUpdateChild.jsm | 12 +++ browser/actors/AboutTBUpdateParent.jsm | 120 +++++++++++++++++++++ browser/actors/moz.build | 6 ++ .../base/content/abouttbupdate/aboutTBUpdate.css | 74 +++++++++++++ .../base/content/abouttbupdate/aboutTBUpdate.js | 27 +++++ .../base/content/abouttbupdate/aboutTBUpdate.xhtml | 39 +++++++ browser/base/content/browser-siteIdentity.js | 4 +- browser/base/content/browser.js | 4 + browser/base/jar.mn | 5 + browser/components/BrowserContentHandler.jsm | 55 +++++++--- browser/components/BrowserGlue.jsm | 15 +++ browser/components/about/AboutRedirector.cpp | 6 ++ browser/components/about/components.conf | 3 + browser/components/moz.build | 5 +- .../locales/en-US/chrome/browser/aboutTBUpdate.dtd | 8 ++ browser/locales/jar.mn | 3 + 16 files changed, 370 insertions(+), 16 deletions(-)
diff --git a/browser/actors/AboutTBUpdateChild.jsm b/browser/actors/AboutTBUpdateChild.jsm new file mode 100644 index 0000000000000..4670da19b3db0 --- /dev/null +++ b/browser/actors/AboutTBUpdateChild.jsm @@ -0,0 +1,12 @@ +// Copyright (c) 2020, The Tor Project, Inc. +// See LICENSE for licensing information. +// +// vim: set sw=2 sts=2 ts=8 et syntax=javascript: + +var EXPORTED_SYMBOLS = ["AboutTBUpdateChild"]; + +const { RemotePageChild } = ChromeUtils.import( + "resource://gre/actors/RemotePageChild.jsm" +); + +class AboutTBUpdateChild extends RemotePageChild {} diff --git a/browser/actors/AboutTBUpdateParent.jsm b/browser/actors/AboutTBUpdateParent.jsm new file mode 100644 index 0000000000000..56a10394565aa --- /dev/null +++ b/browser/actors/AboutTBUpdateParent.jsm @@ -0,0 +1,120 @@ +// Copyright (c) 2020, The Tor Project, Inc. +// See LICENSE for licensing information. +// +// vim: set sw=2 sts=2 ts=8 et syntax=javascript: + +"use strict"; + +this.EXPORTED_SYMBOLS = ["AboutTBUpdateParent"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +const kRequestUpdateMessageName = "FetchUpdateData"; + +/** + * This code provides services to the about:tbupdate page. Whenever + * about:tbupdate needs to do something chrome-privileged, it sends a + * message that's handled here. It is modeled after Mozilla's about:home + * implementation. + */ +class AboutTBUpdateParent extends JSWindowActorParent { + receiveMessage(aMessage) { + if (aMessage.name == kRequestUpdateMessageName) { + return this.releaseNoteInfo; + } + return undefined; + } + + get moreInfoURL() { + try { + return Services.prefs.getCharPref("torbrowser.post_update.url"); + } catch (e) {} + + // Use the default URL as a fallback. + return Services.urlFormatter.formatURLPref("startup.homepage_override_url"); + } + + // Read the text from the beginning of the changelog file that is located + // at TorBrowser/Docs/ChangeLog.txt and return an object that contains + // the following properties: + // version e.g., Tor Browser 8.5 + // releaseDate e.g., March 31 2019 + // releaseNotes details of changes (lines 2 - end of ChangeLog.txt) + // We attempt to parse the first line of ChangeLog.txt to extract the + // version and releaseDate. If parsing fails, we return the entire first + // line in version and omit releaseDate. + // + // On Mac OS, when building with --enable-tor-browser-data-outside-app-dir + // to support Gatekeeper signing, the ChangeLog.txt file is located in + // TorBrowser.app/Contents/Resources/TorBrowser/Docs/. + get releaseNoteInfo() { + let info = { moreInfoURL: this.moreInfoURL }; + + try { + let f; + if (AppConstants.TOR_BROWSER_DATA_OUTSIDE_APP_DIR) { + // "XREExeF".parent is the directory that contains firefox, i.e., + // Browser/ or, on Mac OS, TorBrowser.app/Contents/MacOS/. + f = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent; + if (AppConstants.platform === "macosx") { + f = f.parent; + f.append("Resources"); + } + f.append("TorBrowser"); + } else { + // "DefProfRt" is .../TorBrowser/Data/Browser + f = Services.dirsvc.get("DefProfRt", Ci.nsIFile); + f = f.parent.parent; // Remove "Data/Browser" + } + + f.append("Docs"); + f.append("ChangeLog.txt"); + + let fs = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fs.init(f, -1, 0, 0); + let s = NetUtil.readInputStreamToString(fs, fs.available()); + fs.close(); + + // Truncate at the first empty line. + s = s.replace(/[\r\n][\r\n][\s\S]*$/m, ""); + + // Split into first line (version plus releaseDate) and + // remainder (releaseNotes). + // This first match() uses multiline mode with two capture groups: + // first line: (.*$) + // remaining lines: ([\s\S]+) + // [\s\S] matches all characters including end of line. This trick + // is needed because when using JavaScript regex in multiline mode, + // . does not match an end of line character. + let matchArray = s.match(/(.*$)\s*([\s\S]+)/m); + if (matchArray && matchArray.length == 3) { + info.releaseNotes = matchArray[2]; + let line1 = matchArray[1]; + // Extract the version and releaseDate. The first line looks like: + // Tor Browser 8.5 -- May 1 2019 + // The regex uses two capture groups: + // text that does not include a hyphen: (^[^-]*) + // remaining text: (.*$) + // In between we match optional whitespace, one or more hyphens, and + // optional whitespace by using: \s*-+\s* + matchArray = line1.match(/(^[^-]*)\s*-+\s*(.*$)/); + if (matchArray && matchArray.length == 3) { + info.version = matchArray[1]; + info.releaseDate = matchArray[2]; + } else { + info.version = line1; // Match failed: return entire line in version. + } + } else { + info.releaseNotes = s; // Only one line: use as releaseNotes. + } + } catch (e) {} + + return info; + } +} diff --git a/browser/actors/moz.build b/browser/actors/moz.build index b329f3cfb8ff6..e4f887ab18ca7 100644 --- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -93,3 +93,9 @@ FINAL_TARGET_FILES.actors += [ BROWSER_CHROME_MANIFESTS += [ "test/browser/browser.ini", ] + +if CONFIG["TOR_BROWSER_UPDATE"]: + FINAL_TARGET_FILES.actors += [ + "AboutTBUpdateChild.jsm", + "AboutTBUpdateParent.jsm", + ] diff --git a/browser/base/content/abouttbupdate/aboutTBUpdate.css b/browser/base/content/abouttbupdate/aboutTBUpdate.css new file mode 100644 index 0000000000000..7c1a34b77f176 --- /dev/null +++ b/browser/base/content/abouttbupdate/aboutTBUpdate.css @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019, The Tor Project, Inc. + * See LICENSE for licensing information. + * + * vim: set sw=2 sts=2 ts=8 et syntax=css: + */ + +:root { + --abouttor-text-color: white; + --abouttor-bg-toron-color: #420C5D; +} + +body { + font-family: Helvetica, Arial, sans-serif; + color: var(--abouttor-text-color); + background-color: var(--abouttor-bg-toron-color); + background-attachment: fixed; + background-size: 100% 100%; +} + +a { + color: var(--abouttor-text-color); +} + +.two-column-grid { + display: inline-grid; + grid-template-columns: auto auto; + grid-column-gap: 50px; + margin: 10px 0px 0px 50px; +} + +.two-column-grid div { + margin-top: 40px; + align-self: baseline; /* Align baseline of text across the row. */ +} + +.label-column { + font-size: 14px; + font-weight: 400; +} + +/* + * Use a reduced top margin to bring the row that contains the + * "visit our website" link closer to the row that precedes it. This + * looks better because the "visit our website" row does not have a + * label in the left column. + */ +div.more-info-row { + margin-top: 5px; + font-size: 14px; +} + +#version-content { + font-size: 50px; + font-weight: 300; +} + +body:not([havereleasedate]) .release-date-cell { + display: none; +} + +#releasedate-content { + font-size: 17px; +} + +#releasenotes-label { + align-self: start; /* Anchor "Release Notes" label at the top. */ +} + +#releasenotes-content { + font-family: monospace; + font-size: 15px; + white-space: pre; +} diff --git a/browser/base/content/abouttbupdate/aboutTBUpdate.js b/browser/base/content/abouttbupdate/aboutTBUpdate.js new file mode 100644 index 0000000000000..ec070e2cb1312 --- /dev/null +++ b/browser/base/content/abouttbupdate/aboutTBUpdate.js @@ -0,0 +1,27 @@ +// Copyright (c) 2020, The Tor Project, Inc. +// See LICENSE for licensing information. +// +// vim: set sw=2 sts=2 ts=8 et syntax=javascript: + +/* eslint-env mozilla/frame-script */ + +// aData may contain the following string properties: +// version +// releaseDate +// moreInfoURL +// releaseNotes +function onUpdate(aData) { + document.getElementById("version-content").textContent = aData.version; + if (aData.releaseDate) { + document.body.setAttribute("havereleasedate", "true"); + document.getElementById("releasedate-content").textContent = + aData.releaseDate; + } + if (aData.moreInfoURL) { + document.getElementById("infolink").setAttribute("href", aData.moreInfoURL); + } + document.getElementById("releasenotes-content").textContent = + aData.releaseNotes; +} + +RPMSendQuery("FetchUpdateData").then(onUpdate); diff --git a/browser/base/content/abouttbupdate/aboutTBUpdate.xhtml b/browser/base/content/abouttbupdate/aboutTBUpdate.xhtml new file mode 100644 index 0000000000000..8489cfef5083a --- /dev/null +++ b/browser/base/content/abouttbupdate/aboutTBUpdate.xhtml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html [ + <!ENTITY % htmlDTD + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> + %globalDTD; + <!ENTITY % tbUpdateDTD SYSTEM "chrome://browser/locale/aboutTBUpdate.dtd"> + %tbUpdateDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" /> + <title>&aboutTBUpdate.changelogTitle;</title> + <link rel="stylesheet" type="text/css" + href="chrome://browser/content/abouttbupdate/aboutTBUpdate.css"/> + <script src="chrome://browser/content/abouttbupdate/aboutTBUpdate.js" + type="text/javascript"/> +</head> +<body dir="&locale.dir;"> +<div class="two-column-grid"> + <div class="label-column">&aboutTBUpdate.version;</div> + <div id="version-content"/> + + <div class="label-column release-date-cell">&aboutTBUpdate.releaseDate;</div> + <div id="releasedate-content" class="release-date-cell"/> + + <div class="more-info-row"/> + <div class="more-info-row">&aboutTBUpdate.linkPrefix;<a id="infolink">&aboutTBUpdate.linkLabel;</a>&aboutTBUpdate.linkSuffix;</div> + + <div id="releasenotes-label" + class="label-column">&aboutTBUpdate.releaseNotes;</div> + <div id="releasenotes-content"></div> +</div> +</body> +</html> diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 6901ce71814a3..acbcc79fb21ea 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -57,7 +57,9 @@ var gIdentityHandler = { * RegExp used to decide if an about url should be shown as being part of * the browser UI. */ - _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i, + _secureInternalPages: (AppConstants.TOR_BROWSER_UPDATE ? + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i),
/** * Whether the established HTTPS connection is considered "broken". diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index abca251bf3aa6..fd33c49a86802 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -649,6 +649,10 @@ var gInitialPages = [ "about:welcome", ];
+if (AppConstants.TOR_BROWSER_UPDATE) { + gInitialPages.push("about:tbupdate"); +} + function isInitialPage(url) { if (!(url instanceof Ci.nsIURI)) { try { diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 7be13da2dd5d1..6554f6a5707e5 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -33,6 +33,11 @@ browser.jar: content/browser/aboutTabCrashed.css (content/aboutTabCrashed.css) content/browser/aboutTabCrashed.js (content/aboutTabCrashed.js) content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml) +#ifdef TOR_BROWSER_UPDATE + content/browser/abouttbupdate/aboutTBUpdate.xhtml (content/abouttbupdate/aboutTBUpdate.xhtml) + content/browser/abouttbupdate/aboutTBUpdate.js (content/abouttbupdate/aboutTBUpdate.js) + content/browser/abouttbupdate/aboutTBUpdate.css (content/abouttbupdate/aboutTBUpdate.css) +#endif * content/browser/browser.css (content/browser.css) content/browser/browser.js (content/browser.js) * content/browser/browser.xhtml (content/browser.xhtml) diff --git a/browser/components/BrowserContentHandler.jsm b/browser/components/BrowserContentHandler.jsm index d8e24e6414479..9f6c8a33a7309 100644 --- a/browser/components/BrowserContentHandler.jsm +++ b/browser/components/BrowserContentHandler.jsm @@ -629,6 +629,23 @@ nsBrowserContentHandler.prototype = { } }
+ // Retrieve the home page early so we can compare it against about:tor + // to decide whether or not we need an override page (second tab) after + // an update was applied. + var startPage = ""; + try { + var choice = prefb.getIntPref("browser.startup.page"); + if (choice == 1 || choice == 3) { + startPage = HomePage.get(); + } + } catch (e) { + Cu.reportError(e); + } + + if (startPage == "about:blank") { + startPage = ""; + } + var override; var overridePage = ""; var additionalPage = ""; @@ -674,6 +691,16 @@ nsBrowserContentHandler.prototype = { // into account because that requires waiting for the session file // to be read. If a crash occurs after updating, before restarting, // we may open the startPage in addition to restoring the session. + // + // Tor Browser: Instead of opening the post-update "override page" + // directly, we ensure that about:tor will be opened in a special + // mode that notifies the user that their browser was updated. + // The about:tor page will provide a link to the override page + // where the user can learn more about the update, as well as a + // link to the Tor Browser changelog page (about:tbupdate). The + // override page URL comes from the openURL attribute within the + // updates.xml file or, if no showURL action is present, from the + // startup.homepage_override_url pref. willRestoreSession = SessionStartup.isAutomaticRestoreEnabled();
overridePage = Services.urlFormatter.formatURLPref( @@ -693,6 +720,20 @@ nsBrowserContentHandler.prototype = { overridePage = overridePage.replace("%OLD_VERSION%", old_mstone); overridePage = overridePage.replace("%OLD_TOR_BROWSER_VERSION%", old_tbversion); +#ifdef TOR_BROWSER_UPDATE + if (overridePage) + { + prefb.setCharPref("torbrowser.post_update.url", overridePage); + prefb.setBoolPref("torbrowser.post_update.shouldNotify", true); + // If the user's homepage is about:tor, we will inform them + // about the update on that page; otherwise, we arrange to + // open about:tor in a secondary tab. + if (startPage === "about:tor") + overridePage = ""; + else + overridePage = "about:tor"; + } +#endif break; case OVERRIDE_NEW_BUILD_ID: if (UpdateManager.readyUpdate) { @@ -765,20 +806,6 @@ nsBrowserContentHandler.prototype = { } }
- var startPage = ""; - try { - var choice = prefb.getIntPref("browser.startup.page"); - if (choice == 1 || choice == 3) { - startPage = HomePage.get(); - } - } catch (e) { - Cu.reportError(e); - } - - if (startPage == "about:blank") { - startPage = ""; - } - let skipStartPage = override == OVERRIDE_NEW_PROFILE && prefb.getBoolPref("browser.startup.firstrunSkipsHomepage"); diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 7c81eaaccf775..16e067c363b2f 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -765,6 +765,21 @@ let JSWINDOWACTORS = { }, };
+if (AppConstants.TOR_BROWSER_UPDATE) { + JSWINDOWACTORS["AboutTBUpdate"] = { + parent: { + moduleURI: "resource:///actors/AboutTBUpdateParent.jsm", + }, + child: { + moduleURI: "resource:///actors/AboutTBUpdateChild.jsm", + events: { + DOMWindowCreated: { capture: true }, + }, + }, + matches: ["about:tbupdate"], + }; +} + (function earlyBlankFirstPaint() { let startTime = Cu.now(); if ( diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 21f673f601d26..fd828a630c92a 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -122,6 +122,12 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::HIDE_FROM_ABOUTABOUT}, {"restartrequired", "chrome://browser/content/aboutRestartRequired.xhtml", nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, +#ifdef TOR_BROWSER_UPDATE + {"tbupdate", "chrome://browser/content/abouttbupdate/aboutTBUpdate.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, +#endif {"torconnect", "chrome://browser/content/torconnect/aboutTorConnect.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 733abef1a80f6..0916bb75e1d57 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -31,6 +31,9 @@ pages = [ 'welcomeback', ]
+if defined('TOR_BROWSER_UPDATE'): + pages.append('tbupdate') + Classes = [ { 'cid': '{7e4bb6ad-2fc4-4dc6-89ef-23e8e5ccf980}', diff --git a/browser/components/moz.build b/browser/components/moz.build index ec8fab7fbc8f7..d29df1d3df99a 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -88,11 +88,14 @@ EXTRA_COMPONENTS += [ ]
EXTRA_JS_MODULES += [ - "BrowserContentHandler.jsm", "BrowserGlue.jsm", "distribution.js", ]
+EXTRA_PP_JS_MODULES += [ + "BrowserContentHandler.jsm", +] + BROWSER_CHROME_MANIFESTS += [ "safebrowsing/content/test/browser.ini", "tests/browser/browser.ini", diff --git a/browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd b/browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd new file mode 100644 index 0000000000000..2d1e59b40eafd --- /dev/null +++ b/browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd @@ -0,0 +1,8 @@ +<!ENTITY aboutTBUpdate.changelogTitle "Tor Browser Changelog"> +<!ENTITY aboutTBUpdate.updated "Tor Browser has been updated."> +<!ENTITY aboutTBUpdate.linkPrefix "For the most up-to-date information about this release, "> +<!ENTITY aboutTBUpdate.linkLabel "visit our website"> +<!ENTITY aboutTBUpdate.linkSuffix "."> +<!ENTITY aboutTBUpdate.version "Version"> +<!ENTITY aboutTBUpdate.releaseDate "Release Date"> +<!ENTITY aboutTBUpdate.releaseNotes "Release Notes"> diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn index fd6e0ac768436..90bc7a0d47577 100644 --- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -20,6 +20,9 @@
locale/browser/accounts.properties (%chrome/browser/accounts.properties) locale/browser/app-extension-fields.properties (%chrome/browser/app-extension-fields.properties) +#ifdef TOR_BROWSER_UPDATE + locale/browser/aboutTBUpdate.dtd (%chrome/browser/aboutTBUpdate.dtd) +#endif locale/browser/browser.dtd (%chrome/browser/browser.dtd) locale/browser/browser.properties (%chrome/browser/browser.properties) locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit fa00b9ff173d247035ac67bbc1c297e5ddf33f9a Author: Georg Koppen gk@torproject.org AuthorDate: Fri Jan 17 12:54:31 2020 +0000
Bug 32658: Create a new MAR signing key
It's time for our rotation again: Move the backup key in the front position and add a new backup key.
Bug 33803: Move our primary nightly MAR signing key to tor-browser
Bug 33803: Add a secondary nightly MAR signing key --- .../update/updater/nightly_aurora_level3_primary.der | Bin 1225 -> 1245 bytes .../updater/nightly_aurora_level3_secondary.der | Bin 1225 -> 1245 bytes toolkit/mozapps/update/updater/release_primary.der | Bin 1225 -> 1229 bytes toolkit/mozapps/update/updater/release_secondary.der | Bin 1225 -> 1229 bytes 4 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der index 44fd95dcff89a..d579cf801e1a3 100644 Binary files a/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der and b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der differ diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der index 90f8e6e82c63a..7cbfa77d06e7a 100644 Binary files a/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der and b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der differ diff --git a/toolkit/mozapps/update/updater/release_primary.der b/toolkit/mozapps/update/updater/release_primary.der index 1d94f88ad73b0..0103a171de88a 100644 Binary files a/toolkit/mozapps/update/updater/release_primary.der and b/toolkit/mozapps/update/updater/release_primary.der differ diff --git a/toolkit/mozapps/update/updater/release_secondary.der b/toolkit/mozapps/update/updater/release_secondary.der index 474706c4b73c9..fcee3944e9b7a 100644 Binary files a/toolkit/mozapps/update/updater/release_secondary.der and b/toolkit/mozapps/update/updater/release_secondary.der differ
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit e1f63c65648906b960bf356d0e28dd07b90dfe07 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Fri May 5 03:41:57 2017 -0700
Omnibox: Add DDG, Startpage, Disconnect, Youtube, Twitter; remove Amazon, eBay, bing
eBay and Amazon don't treat Tor users very well. Accounts often get locked and payments reversed.
Also: Bug 16322: Update DuckDuckGo search engine
We are replacing the clearnet URL with an onion service one (thanks to a patch by a cypherpunk) and are removing the duplicated DDG search engine. Duplicating DDG happend due to bug 1061736 where Mozilla included DDG itself into Firefox. Interestingly, this caused breaking the DDG search if JavaScript is disabled as the Mozilla engine, which gets loaded earlier, does not use the html version of the search page. Moreover, the Mozilla engine tracked where the users were searching from by adding a respective parameter to the search query. We got rid of that feature as well.
Also: This fixes bug 20809: the DuckDuckGo team has changed its server-side code in a way that lets users with JavaScript enabled use the default landing page while those without JavaScript available get redirected directly to the non-JS page. We adapt the search engine URLs accordingly.
Also fixes bug 29798 by making sure we only specify the Google search engine we actually ship an .xml file for.
Also regression tests.
squash! Omnibox: Add DDG, Startpage, Disconnect, Youtube, Twitter; remove Amazon, eBay, bing
Bug 40494: Update Startpage search provider
squash! Omnibox: Add DDG, Startpage, Disconnect, Youtube, Twitter; remove Amazon, eBay, bing
Bug 40438: Add Blockchair as a search engine
Bug 33342: Avoid disconnect search addon error after removal.
We removed the addon in #32767, but it was still being loaded from addonStartup.json.lz4 and throwing an error on startup because its resource: location is not available anymore. --- .../search/extensions/blockchair-onion/favicon.png | Bin 0 -> 3116 bytes .../extensions/blockchair-onion/manifest.json | 26 ++++++++++++++ .../search/extensions/blockchair/favicon.png | Bin 0 -> 2898 bytes .../search/extensions/blockchair/manifest.json | 26 ++++++++++++++ .../search/extensions/ddg-onion/favicon.ico | Bin 0 -> 973 bytes .../search/extensions/ddg-onion/manifest.json | 26 ++++++++++++++ .../components/search/extensions/ddg/favicon.ico | Bin 5430 -> 0 bytes .../components/search/extensions/ddg/favicon.png | Bin 0 -> 1150 bytes .../components/search/extensions/ddg/manifest.json | 38 ++------------------- .../extensions/google/_locales/b-1-d/messages.json | 23 ------------- .../extensions/google/_locales/b-1-e/messages.json | 23 ------------- .../extensions/google/_locales/b-d/messages.json | 23 ------------- .../extensions/google/_locales/b-e/messages.json | 23 ------------- .../extensions/google/_locales/en/messages.json | 24 ------------- .../search/extensions/google/manifest.json | 17 +++++---- .../search/extensions/startpage/favicon.png | Bin 0 -> 1150 bytes .../search/extensions/startpage/manifest.json | 26 ++++++++++++++ .../search/extensions/twitter/favicon.ico | Bin 0 -> 1650 bytes .../search/extensions/twitter/manifest.json | 26 ++++++++++++++ .../extensions/wikipedia/_locales/NN/messages.json | 20 ----------- .../extensions/wikipedia/_locales/NO/messages.json | 20 ----------- .../extensions/wikipedia/_locales/af/messages.json | 20 ----------- .../extensions/wikipedia/_locales/an/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ar/messages.json | 20 ----------- .../wikipedia/_locales/ast/messages.json | 20 ----------- .../extensions/wikipedia/_locales/az/messages.json | 20 ----------- .../wikipedia/_locales/be-tarask/messages.json | 20 ----------- .../extensions/wikipedia/_locales/be/messages.json | 20 ----------- .../extensions/wikipedia/_locales/bg/messages.json | 20 ----------- .../extensions/wikipedia/_locales/bn/messages.json | 20 ----------- .../extensions/wikipedia/_locales/br/messages.json | 20 ----------- .../extensions/wikipedia/_locales/bs/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ca/messages.json | 20 ----------- .../extensions/wikipedia/_locales/cy/messages.json | 20 ----------- .../extensions/wikipedia/_locales/cz/messages.json | 20 ----------- .../extensions/wikipedia/_locales/da/messages.json | 20 ----------- .../extensions/wikipedia/_locales/de/messages.json | 20 ----------- .../wikipedia/_locales/dsb/messages.json | 20 ----------- .../extensions/wikipedia/_locales/el/messages.json | 20 ----------- .../extensions/wikipedia/_locales/en/messages.json | 20 ----------- .../extensions/wikipedia/_locales/eo/messages.json | 20 ----------- .../extensions/wikipedia/_locales/es/messages.json | 20 ----------- .../extensions/wikipedia/_locales/et/messages.json | 20 ----------- .../extensions/wikipedia/_locales/eu/messages.json | 20 ----------- .../extensions/wikipedia/_locales/fa/messages.json | 20 ----------- .../extensions/wikipedia/_locales/fi/messages.json | 20 ----------- .../extensions/wikipedia/_locales/fr/messages.json | 20 ----------- .../wikipedia/_locales/fy-NL/messages.json | 20 ----------- .../wikipedia/_locales/ga-IE/messages.json | 20 ----------- .../extensions/wikipedia/_locales/gd/messages.json | 20 ----------- .../extensions/wikipedia/_locales/gl/messages.json | 20 ----------- .../extensions/wikipedia/_locales/gn/messages.json | 20 ----------- .../extensions/wikipedia/_locales/gu/messages.json | 20 ----------- .../extensions/wikipedia/_locales/he/messages.json | 20 ----------- .../extensions/wikipedia/_locales/hi/messages.json | 20 ----------- .../extensions/wikipedia/_locales/hr/messages.json | 20 ----------- .../wikipedia/_locales/hsb/messages.json | 20 ----------- .../extensions/wikipedia/_locales/hu/messages.json | 20 ----------- .../extensions/wikipedia/_locales/hy/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ia/messages.json | 20 ----------- .../extensions/wikipedia/_locales/id/messages.json | 20 ----------- .../extensions/wikipedia/_locales/is/messages.json | 20 ----------- .../extensions/wikipedia/_locales/it/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ja/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ka/messages.json | 20 ----------- .../wikipedia/_locales/kab/messages.json | 20 ----------- .../extensions/wikipedia/_locales/kk/messages.json | 20 ----------- .../extensions/wikipedia/_locales/km/messages.json | 20 ----------- .../extensions/wikipedia/_locales/kn/messages.json | 20 ----------- .../extensions/wikipedia/_locales/kr/messages.json | 20 ----------- .../wikipedia/_locales/lij/messages.json | 20 ----------- .../extensions/wikipedia/_locales/lo/messages.json | 20 ----------- .../extensions/wikipedia/_locales/lt/messages.json | 20 ----------- .../wikipedia/_locales/ltg/messages.json | 20 ----------- .../extensions/wikipedia/_locales/lv/messages.json | 20 ----------- .../extensions/wikipedia/_locales/mk/messages.json | 20 ----------- .../extensions/wikipedia/_locales/mr/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ms/messages.json | 20 ----------- .../extensions/wikipedia/_locales/my/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ne/messages.json | 20 ----------- .../extensions/wikipedia/_locales/nl/messages.json | 20 ----------- .../extensions/wikipedia/_locales/oc/messages.json | 20 ----------- .../extensions/wikipedia/_locales/pa/messages.json | 20 ----------- .../extensions/wikipedia/_locales/pl/messages.json | 20 ----------- .../extensions/wikipedia/_locales/pt/messages.json | 20 ----------- .../extensions/wikipedia/_locales/rm/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ro/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ru/messages.json | 20 ----------- .../extensions/wikipedia/_locales/si/messages.json | 20 ----------- .../extensions/wikipedia/_locales/sk/messages.json | 20 ----------- .../extensions/wikipedia/_locales/sl/messages.json | 20 ----------- .../extensions/wikipedia/_locales/sq/messages.json | 20 ----------- .../extensions/wikipedia/_locales/sr/messages.json | 20 ----------- .../wikipedia/_locales/sv-SE/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ta/messages.json | 20 ----------- .../extensions/wikipedia/_locales/te/messages.json | 20 ----------- .../extensions/wikipedia/_locales/th/messages.json | 20 ----------- .../extensions/wikipedia/_locales/tl/messages.json | 20 ----------- .../extensions/wikipedia/_locales/tr/messages.json | 20 ----------- .../extensions/wikipedia/_locales/uk/messages.json | 20 ----------- .../extensions/wikipedia/_locales/ur/messages.json | 20 ----------- .../extensions/wikipedia/_locales/uz/messages.json | 20 ----------- .../extensions/wikipedia/_locales/vi/messages.json | 20 ----------- .../extensions/wikipedia/_locales/wo/messages.json | 20 ----------- .../wikipedia/_locales/zh-CN/messages.json | 20 ----------- .../wikipedia/_locales/zh-TW/messages.json | 20 ----------- .../search/extensions/wikipedia/manifest.json | 15 ++++---- .../components/search/extensions/yahoo/favicon.ico | Bin 0 -> 5430 bytes .../search/extensions/yahoo/manifest.json | 28 +++++++++++++++ .../search/extensions/youtube/favicon.ico | Bin 0 -> 1150 bytes .../search/extensions/youtube/manifest.json | 26 ++++++++++++++ tbb-tests/browser_tor_omnibox.js | 20 +++++++++++ toolkit/components/search/SearchService.jsm | 30 +++++++--------- .../mozapps/extensions/internal/XPIProvider.jsm | 6 ++++ 114 files changed, 241 insertions(+), 1925 deletions(-)
diff --git a/browser/components/search/extensions/blockchair-onion/favicon.png b/browser/components/search/extensions/blockchair-onion/favicon.png new file mode 100644 index 0000000000000..92d832ded172d Binary files /dev/null and b/browser/components/search/extensions/blockchair-onion/favicon.png differ diff --git a/browser/components/search/extensions/blockchair-onion/manifest.json b/browser/components/search/extensions/blockchair-onion/manifest.json new file mode 100644 index 0000000000000..e7d15ee50247f --- /dev/null +++ b/browser/components/search/extensions/blockchair-onion/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "BlockchairOnion", + "description": "Blockchair Onion", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "blockchair-onion@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.png" + }, + "web_accessible_resources": [ + "favicon.png" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "BlockchairOnion", + "search_url": "http://blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion/search", + "search_form": "http://blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion/search...", + "search_url_post_params": "q={searchTerms}" + } + } +} diff --git a/browser/components/search/extensions/blockchair/favicon.png b/browser/components/search/extensions/blockchair/favicon.png new file mode 100644 index 0000000000000..f4869e87e0086 Binary files /dev/null and b/browser/components/search/extensions/blockchair/favicon.png differ diff --git a/browser/components/search/extensions/blockchair/manifest.json b/browser/components/search/extensions/blockchair/manifest.json new file mode 100644 index 0000000000000..0f16b9049cf2e --- /dev/null +++ b/browser/components/search/extensions/blockchair/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "Blockchair", + "description": "Blockchair", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "blockchair@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.png" + }, + "web_accessible_resources": [ + "favicon.png" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "Blockchair", + "search_url": "https://blockchair.com/search", + "search_form": "https://blockchair.com/search/?q=%7BsearchTerms%7D", + "search_url_get_params": "q={searchTerms}" + } + } +} diff --git a/browser/components/search/extensions/ddg-onion/favicon.ico b/browser/components/search/extensions/ddg-onion/favicon.ico new file mode 100644 index 0000000000000..13c325f6585f6 Binary files /dev/null and b/browser/components/search/extensions/ddg-onion/favicon.ico differ diff --git a/browser/components/search/extensions/ddg-onion/manifest.json b/browser/components/search/extensions/ddg-onion/manifest.json new file mode 100644 index 0000000000000..49f3c116106be --- /dev/null +++ b/browser/components/search/extensions/ddg-onion/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "DuckDuckGoOnion", + "description": "Duck Duck Go Onion", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "ddg-onion@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.ico" + }, + "web_accessible_resources": [ + "favicon.ico" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "DuckDuckGoOnion", + "search_url": "https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion", + "search_form": "https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/?q=%7...", + "search_url_get_params": "q={searchTerms}" + } + } +} diff --git a/browser/components/search/extensions/ddg/favicon.ico b/browser/components/search/extensions/ddg/favicon.ico deleted file mode 100644 index 560000b03e2c0..0000000000000 Binary files a/browser/components/search/extensions/ddg/favicon.ico and /dev/null differ diff --git a/browser/components/search/extensions/ddg/favicon.png b/browser/components/search/extensions/ddg/favicon.png new file mode 100644 index 0000000000000..c853b95b89efd Binary files /dev/null and b/browser/components/search/extensions/ddg/favicon.png differ diff --git a/browser/components/search/extensions/ddg/manifest.json b/browser/components/search/extensions/ddg/manifest.json index 782b860a679f1..4a3b0ad20fe91 100644 --- a/browser/components/search/extensions/ddg/manifest.json +++ b/browser/components/search/extensions/ddg/manifest.json @@ -10,50 +10,18 @@ }, "hidden": true, "icons": { - "16": "favicon.ico" + "16": "favicon.png" }, "web_accessible_resources": [ - "favicon.ico" + "favicon.png" ], "chrome_settings_overrides": { "search_provider": { "keyword": ["@duckduckgo", "@ddg"], "name": "DuckDuckGo", - "search_url": "https://duckduckgo.com/", + "search_url": "https://duckduckgo.com", "search_form": "https://duckduckgo.com/?q=%7BsearchTerms%7D", "search_url_get_params": "q={searchTerms}", - "params": [ - { - "name": "t", - "condition": "purpose", - "purpose": "contextmenu", - "value": "ffcm" - }, - { - "name": "t", - "condition": "purpose", - "purpose": "keyword", - "value": "ffab" - }, - { - "name": "t", - "condition": "purpose", - "purpose": "searchbar", - "value": "ffsb" - }, - { - "name": "t", - "condition": "purpose", - "purpose": "homepage", - "value": "ffhp" - }, - { - "name": "t", - "condition": "purpose", - "purpose": "newtab", - "value": "ffnt" - } - ], "suggest_url": "https://ac.duckduckgo.com/ac/", "suggest_url_get_params": "q={searchTerms}&type=list" } diff --git a/browser/components/search/extensions/google/_locales/b-1-d/messages.json b/browser/components/search/extensions/google/_locales/b-1-d/messages.json deleted file mode 100644 index 1b9d05307d64e..0000000000000 --- a/browser/components/search/extensions/google/_locales/b-1-d/messages.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.com/search" - }, - "searchForm": { - "message": "https://www.google.com/search?client=firefox-b-1-d&q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.com/complete/search?client=firefox&q=%7BsearchTerms%7..." - }, - "searchUrlGetParams": { - "message": "client=firefox-b-1-d&q={searchTerms}" - }, - "channelPref": { - "message": "google_channel_us" - } -} diff --git a/browser/components/search/extensions/google/_locales/b-1-e/messages.json b/browser/components/search/extensions/google/_locales/b-1-e/messages.json deleted file mode 100644 index b470cd8443316..0000000000000 --- a/browser/components/search/extensions/google/_locales/b-1-e/messages.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.com/search" - }, - "searchForm": { - "message": "https://www.google.com/search?client=firefox-b-1-e&q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.com/complete/search?client=firefox&q=%7BsearchTerms%7..." - }, - "searchUrlGetParams": { - "message": "client=firefox-b-1-e&q={searchTerms}" - }, - "channelPref": { - "message": "google_channel_us" - } -} diff --git a/browser/components/search/extensions/google/_locales/b-d/messages.json b/browser/components/search/extensions/google/_locales/b-d/messages.json deleted file mode 100644 index a6423089d9f9c..0000000000000 --- a/browser/components/search/extensions/google/_locales/b-d/messages.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.com/search" - }, - "searchForm": { - "message": "https://www.google.com/search?client=firefox-b-d&q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.com/complete/search?client=firefox&q=%7BsearchTerms%7..." - }, - "searchUrlGetParams": { - "message": "client=firefox-b-d&q={searchTerms}" - }, - "channelPref": { - "message": "google_channel_row" - } -} diff --git a/browser/components/search/extensions/google/_locales/b-e/messages.json b/browser/components/search/extensions/google/_locales/b-e/messages.json deleted file mode 100644 index 70939ee000743..0000000000000 --- a/browser/components/search/extensions/google/_locales/b-e/messages.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.com/search" - }, - "searchForm": { - "message": "https://www.google.com/search?client=firefox-b-e&q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.com/complete/search?client=firefox&q=%7BsearchTerms%7..." - }, - "searchUrlGetParams": { - "message": "client=firefox-b-e&q={searchTerms}" - }, - "channelPref": { - "message": "google_channel_row" - } -} diff --git a/browser/components/search/extensions/google/_locales/en/messages.json b/browser/components/search/extensions/google/_locales/en/messages.json deleted file mode 100644 index aeca0ef128b3c..0000000000000 --- a/browser/components/search/extensions/google/_locales/en/messages.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.com/search" - }, - "searchForm": { - "message": "https://www.google.com/search?client=firefox-b-d&q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.com/complete/search?client=firefox&q=%7BsearchTerms%7..." - }, - "searchUrlGetParams": { - "message": "client=firefox-b-d&q={searchTerms}" - }, - "channelPref": { - "message": "google_channel_row" - } - -} diff --git a/browser/components/search/extensions/google/manifest.json b/browser/components/search/extensions/google/manifest.json index 6cd4d2a42358e..e84a269e4ea10 100644 --- a/browser/components/search/extensions/google/manifest.json +++ b/browser/components/search/extensions/google/manifest.json @@ -1,6 +1,6 @@ { - "name": "__MSG_extensionName__", - "description": "__MSG_extensionDescription__", + "name": "Google", + "description": "Google Search", "manifest_version": 2, "version": "1.2", "applications": { @@ -9,7 +9,6 @@ } }, "hidden": true, - "default_locale": "en", "icons": { "16": "favicon.ico" }, @@ -19,18 +18,18 @@ "chrome_settings_overrides": { "search_provider": { "keyword": "@google", - "name": "__MSG_extensionName__", - "search_url": "__MSG_searchUrl__", - "search_form": "__MSG_searchForm__", - "suggest_url": "__MSG_suggestUrl__", + "name": "Google", + "search_url": "https://www.google.com/search", + "search_form": "https://www.google.com/search?client=firefox-b-d&q=%7BsearchTerms%7D", + "suggest_url": "https://www.google.com/complete/search?client=firefox&q=%7BsearchTerms%7...", "params": [ { "name": "channel", "condition": "pref", - "pref": "__MSG_channelPref__" + "pref": "google_channel_row" } ], - "search_url_get_params": "__MSG_searchUrlGetParams__" + "search_url_get_params": "client=firefox-b-d&q={searchTerms}" } } } diff --git a/browser/components/search/extensions/startpage/favicon.png b/browser/components/search/extensions/startpage/favicon.png new file mode 100644 index 0000000000000..44b94a986fd2e Binary files /dev/null and b/browser/components/search/extensions/startpage/favicon.png differ diff --git a/browser/components/search/extensions/startpage/manifest.json b/browser/components/search/extensions/startpage/manifest.json new file mode 100644 index 0000000000000..18041d3a2f836 --- /dev/null +++ b/browser/components/search/extensions/startpage/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "Startpage", + "description": "Start Page", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "startpage@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.png" + }, + "web_accessible_resources": [ + "favicon.png" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "Startpage", + "search_url": "https://startpage.com/sp/search", + "search_form": "https://startpage.com/sp/search/", + "search_url_post_params": "q={searchTerms}&segment=startpage.tor" + } + } +} diff --git a/browser/components/search/extensions/twitter/favicon.ico b/browser/components/search/extensions/twitter/favicon.ico new file mode 100644 index 0000000000000..e5aaff437912d Binary files /dev/null and b/browser/components/search/extensions/twitter/favicon.ico differ diff --git a/browser/components/search/extensions/twitter/manifest.json b/browser/components/search/extensions/twitter/manifest.json new file mode 100644 index 0000000000000..59714e0e10450 --- /dev/null +++ b/browser/components/search/extensions/twitter/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "Twitter", + "description": "Realtime Twitter Search", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "twitter@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.ico" + }, + "web_accessible_resources": [ + "favicon.ico" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "Twitter", + "search_url": "https://twitter.com/search", + "search_form": "https://twitter.com/search?q=%7BsearchTerms%7D&partner=Firefox&sourc...", + "search_url_get_params": "q={searchTerms}&partner=Firefox&source=desktop-search" + } + } +} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/NN/messages.json b/browser/components/search/extensions/wikipedia/_locales/NN/messages.json deleted file mode 100644 index e4ee66bc780dd..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/NN/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (nn)" - }, - "extensionDescription": { - "message": "Wikipedia, det frie oppslagsverket" - }, - "searchUrl": { - "message": "https://nn.wikipedia.org/wiki/Spesial:S%C3%B8k" - }, - "searchForm": { - "message": "https://nn.wikipedia.org/wiki/Spesial:S%C3%B8k?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://nn.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/NO/messages.json b/browser/components/search/extensions/wikipedia/_locales/NO/messages.json deleted file mode 100644 index ec016ac7337eb..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/NO/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (no)" - }, - "extensionDescription": { - "message": "Wikipedia, den frie encyklopedi" - }, - "searchUrl": { - "message": "https://no.wikipedia.org/wiki/Spesial:S%C3%B8k" - }, - "searchForm": { - "message": "https://no.wikipedia.org/wiki/Spesial:S%C3%B8k?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://no.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/af/messages.json b/browser/components/search/extensions/wikipedia/_locales/af/messages.json deleted file mode 100644 index 8cf9de8ac9b32..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/af/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (af)" - }, - "extensionDescription": { - "message": "Wikipedia, die vrye ensiklopedie" - }, - "searchUrl": { - "message": "https://af.wikipedia.org/wiki/Spesiaal:Soek" - }, - "searchForm": { - "message": "https://af.wikipedia.org/wiki/Spesiaal:Soek?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://af.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/an/messages.json b/browser/components/search/extensions/wikipedia/_locales/an/messages.json deleted file mode 100644 index e8cce665c96ed..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/an/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Biquipedia (an)" - }, - "extensionDescription": { - "message": "A enciclopedia Libre" - }, - "searchUrl": { - "message": "https://an.wikipedia.org/wiki/Especial:Mirar" - }, - "searchForm": { - "message": "https://an.wikipedia.org/wiki/Especial:Mirar?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://an.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ar/messages.json b/browser/components/search/extensions/wikipedia/_locales/ar/messages.json deleted file mode 100644 index de90b2a2055eb..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ar/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "ويكيبيديا (ar)" - }, - "extensionDescription": { - "message": "ويكيبيديا (ar)" - }, - "searchUrl": { - "message": "https://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB" - }, - "searchForm": { - "message": "https://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB?search=%..." - }, - "suggestUrl": { - "message": "https://ar.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ast/messages.json b/browser/components/search/extensions/wikipedia/_locales/ast/messages.json deleted file mode 100644 index a127ba07f29b2..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ast/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (ast)" - }, - "extensionDescription": { - "message": "La enciclopedia llibre" - }, - "searchUrl": { - "message": "https://ast.wikipedia.org/wiki/Especial:Gueta" - }, - "searchForm": { - "message": "https://ast.wikipedia.org/wiki/Especial:Gueta?search=%7BsearchTerms%7D&s..." - }, - "suggestUrl": { - "message": "https://ast.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTe..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/az/messages.json b/browser/components/search/extensions/wikipedia/_locales/az/messages.json deleted file mode 100644 index f551a717e6d34..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/az/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipediya (az)" - }, - "extensionDescription": { - "message": "Vikipediya, açıq ensiklopediya" - }, - "searchUrl": { - "message": "https://az.wikipedia.org/wiki/X%C3%BCsusi:Axtar" - }, - "searchForm": { - "message": "https://az.wikipedia.org/wiki/X%C3%BCsusi:Axtar?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://az.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/be-tarask/messages.json b/browser/components/search/extensions/wikipedia/_locales/be-tarask/messages.json deleted file mode 100644 index aecfecf2fb190..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/be-tarask/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Вікіпэдыя (be-tarask)" - }, - "extensionDescription": { - "message": "Вікіпэдыя, вольная энцыкляпэдыя" - }, - "searchUrl": { - "message": "https://be-tarask.wikipedia.org/wiki/%D0%A1%D0%BF%D1%8D%D1%86%D1%8B%D1%8F%D0..." - }, - "searchForm": { - "message": "https://be-tarask.wikipedia.org/wiki/%D0%A1%D0%BF%D1%8D%D1%86%D1%8B%D1%8F%D0..." - }, - "suggestUrl": { - "message": "https://be-tarask.wikipedia.org/w/api.php?action=opensearch&search=%7Bse..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/be/messages.json b/browser/components/search/extensions/wikipedia/_locales/be/messages.json deleted file mode 100644 index 6aa763451e677..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/be/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Вікіпедыя (be)" - }, - "extensionDescription": { - "message": "Вікіпедыя, свабодная энцыклапедыя" - }, - "searchUrl": { - "message": "https://be.wikipedia.org/wiki/%D0%90%D0%B4%D0%BC%D1%8B%D1%81%D0%BB%D0%BE%D0%..." - }, - "searchForm": { - "message": "https://be.wikipedia.org/wiki/%D0%90%D0%B4%D0%BC%D1%8B%D1%81%D0%BB%D0%BE%D0%..." - }, - "suggestUrl": { - "message": "https://be.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/bg/messages.json b/browser/components/search/extensions/wikipedia/_locales/bg/messages.json deleted file mode 100644 index 896a85d66b877..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/bg/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Уикипедия (bg)" - }, - "extensionDescription": { - "message": "Уикипедия, свободната енциклоподия" - }, - "searchUrl": { - "message": "https://bg.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B5%D1%86%D0%B8%D0%B0%D0%BB%D0%..." - }, - "searchForm": { - "message": "https://bg.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B5%D1%86%D0%B8%D0%B0%D0%BB%D0%..." - }, - "suggestUrl": { - "message": "https://bg.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/bn/messages.json b/browser/components/search/extensions/wikipedia/_locales/bn/messages.json deleted file mode 100644 index fe9887ed1938a..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/bn/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "উইকিপিডিয়া (bn)" - }, - "extensionDescription": { - "message": "উইকিপিডিয়া, মুক্ত বিশ্বকোষ" - }, - "searchUrl": { - "message": "https://bn.wikipedia.org/wiki/%E0%A6%AC%E0%A6%BF%E0%A6%B6%E0%A7%87%E0%A6%B7:..." - }, - "searchForm": { - "message": "https://bn.wikipedia.org/wiki/%E0%A6%AC%E0%A6%BF%E0%A6%B6%E0%A7%87%E0%A6%B7:..." - }, - "suggestUrl": { - "message": "https://bn.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/br/messages.json b/browser/components/search/extensions/wikipedia/_locales/br/messages.json deleted file mode 100644 index 33869ce8e752f..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/br/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (br)" - }, - "extensionDescription": { - "message": "Wikipedia, an holloueziadur digor" - }, - "searchUrl": { - "message": "https://br.wikipedia.org/wiki/Dibar:Klask" - }, - "searchForm": { - "message": "https://br.wikipedia.org/wiki/Dibar:Klask?search=%7BsearchTerms%7D&sourc..." - }, - "suggestUrl": { - "message": "https://br.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/bs/messages.json b/browser/components/search/extensions/wikipedia/_locales/bs/messages.json deleted file mode 100644 index 746150e3d8e84..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/bs/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (bs)" - }, - "extensionDescription": { - "message": "Slobodna enciklopedija" - }, - "searchUrl": { - "message": "https://bs.wikipedia.org/wiki/Posebno:Pretraga" - }, - "searchForm": { - "message": "https://bs.wikipedia.org/wiki/Posebno:Pretraga?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://bs.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ca/messages.json b/browser/components/search/extensions/wikipedia/_locales/ca/messages.json deleted file mode 100644 index 151ec1a71ba53..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ca/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Viquipèdia (ca)" - }, - "extensionDescription": { - "message": "L'enciclopèdia lliure" - }, - "searchUrl": { - "message": "https://ca.wikipedia.org/wiki/Especial:Cerca" - }, - "searchForm": { - "message": "https://ca.wikipedia.org/wiki/Especial:Cerca?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://ca.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/cy/messages.json b/browser/components/search/extensions/wikipedia/_locales/cy/messages.json deleted file mode 100644 index cfed7c73be34f..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/cy/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wicipedia (cy)" - }, - "extensionDescription": { - "message": "Wicipedia, Y Gwyddioniadur Rhydd" - }, - "searchUrl": { - "message": "https://cy.wikipedia.org/wiki/Arbennig:Search" - }, - "searchForm": { - "message": "https://cy.wikipedia.org/wiki/Arbennig:Search?search=%7BsearchTerms%7D&s..." - }, - "suggestUrl": { - "message": "https://cy.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/cz/messages.json b/browser/components/search/extensions/wikipedia/_locales/cz/messages.json deleted file mode 100644 index 12f7eb22d711b..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/cz/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedie (cs)" - }, - "extensionDescription": { - "message": "Wikipedia, svobodná encyclopedie" - }, - "searchUrl": { - "message": "https://cs.wikipedia.org/wiki/Speci%C3%A1ln%C3%AD:Hled%C3%A1n%C3%AD" - }, - "searchForm": { - "message": "https://cs.wikipedia.org/wiki/Speci%C3%A1ln%C3%AD:Hled%C3%A1n%C3%AD?search=%..." - }, - "suggestUrl": { - "message": "https://cs.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/da/messages.json b/browser/components/search/extensions/wikipedia/_locales/da/messages.json deleted file mode 100644 index 801d5a5183ccd..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/da/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (da)" - }, - "extensionDescription": { - "message": "Wikipedia, den frie encyklopædi" - }, - "searchUrl": { - "message": "https://da.wikipedia.org/wiki/Speciel:S%C3%B8gning" - }, - "searchForm": { - "message": "https://da.wikipedia.org/wiki/Speciel:S%C3%B8gning?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://da.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/de/messages.json b/browser/components/search/extensions/wikipedia/_locales/de/messages.json deleted file mode 100644 index 0e6bbe8905ca6..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/de/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (de)" - }, - "extensionDescription": { - "message": "Wikipedia, die freie Enzyklopädie" - }, - "searchUrl": { - "message": "https://de.wikipedia.org/wiki/Spezial:Suche" - }, - "searchForm": { - "message": "https://de.wikipedia.org/wiki/Spezial:Suche?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://de.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/dsb/messages.json b/browser/components/search/extensions/wikipedia/_locales/dsb/messages.json deleted file mode 100644 index ffca44b5f7fb2..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/dsb/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedija (dsb)" - }, - "extensionDescription": { - "message": "Wikipedija, lichotna encyklopedija" - }, - "searchUrl": { - "message": "https://dsb.wikipedia.org/wiki/Specialne:Pyta%C5%9B" - }, - "searchForm": { - "message": "https://dsb.wikipedia.org/wiki/Specialne:Pyta%C5%9B?search=%7BsearchTerms%7D..." - }, - "suggestUrl": { - "message": "https://dsb.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTe..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/el/messages.json b/browser/components/search/extensions/wikipedia/_locales/el/messages.json deleted file mode 100644 index 95b48f3d9ca7b..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/el/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (el)" - }, - "extensionDescription": { - "message": "Βικιπαίδεια, η ελεύθερη εγκυκλοπαίδεια" - }, - "searchUrl": { - "message": "https://el.wikipedia.org/wiki/%CE%95%CE%B9%CE%B4%CE%B9%CE%BA%CF%8C:%CE%91%CE..." - }, - "searchForm": { - "message": "https://el.wikipedia.org/wiki/%CE%95%CE%B9%CE%B4%CE%B9%CE%BA%CF%8C:%CE%91%CE..." - }, - "suggestUrl": { - "message": "https://el.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/en/messages.json b/browser/components/search/extensions/wikipedia/_locales/en/messages.json deleted file mode 100644 index 0de3c9a8071a7..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/en/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (en)" - }, - "extensionDescription": { - "message": "Wikipedia, the Free Encyclopedia" - }, - "searchUrl": { - "message": "https://en.wikipedia.org/wiki/Special:Search" - }, - "searchForm": { - "message": "https://en.wikipedia.org/wiki/Special:Search?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://en.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/eo/messages.json b/browser/components/search/extensions/wikipedia/_locales/eo/messages.json deleted file mode 100644 index 10aa88dd11ba8..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/eo/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipedio (eo)" - }, - "extensionDescription": { - "message": "Vikipedio, la libera enciklopedio" - }, - "searchUrl": { - "message": "https://eo.wikipedia.org/wiki/Speciala%C4%B5o:Ser%C4%89i" - }, - "searchForm": { - "message": "https://eo.wikipedia.org/wiki/Speciala%C4%B5o:Ser%C4%89i?search=%7BsearchTer..." - }, - "suggestUrl": { - "message": "https://eo.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/es/messages.json b/browser/components/search/extensions/wikipedia/_locales/es/messages.json deleted file mode 100644 index 09ec1f757657d..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/es/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (es)" - }, - "extensionDescription": { - "message": "Wikipedia, la enciclopedia libre" - }, - "searchUrl": { - "message": "https://es.wikipedia.org/wiki/Especial:Buscar" - }, - "searchForm": { - "message": "https://es.wikipedia.org/wiki/Especial:Buscar?search=%7BsearchTerms%7D&s..." - }, - "suggestUrl": { - "message": "https://es.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/et/messages.json b/browser/components/search/extensions/wikipedia/_locales/et/messages.json deleted file mode 100644 index 91363fbb392b7..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/et/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipeedia (et)" - }, - "extensionDescription": { - "message": "Vikipeedia, vaba entsüklopeedia" - }, - "searchUrl": { - "message": "https://et.wikipedia.org/wiki/Eri:Otsimine" - }, - "searchForm": { - "message": "https://et.wikipedia.org/wiki/Eri:Otsimine?search=%7BsearchTerms%7D&sour..." - }, - "suggestUrl": { - "message": "https://et.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/eu/messages.json b/browser/components/search/extensions/wikipedia/_locales/eu/messages.json deleted file mode 100644 index 1bd7027dec54f..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/eu/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (eu)" - }, - "extensionDescription": { - "message": "Wikipedia, entziklopedia askea" - }, - "searchUrl": { - "message": "https://eu.wikipedia.org/wiki/Berezi:Bilatu" - }, - "searchForm": { - "message": "https://eu.wikipedia.org/wiki/Berezi:Bilatu?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://eu.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/fa/messages.json b/browser/components/search/extensions/wikipedia/_locales/fa/messages.json deleted file mode 100644 index 9fdc964a1e0b1..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/fa/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "ویکیپدیا (fa)" - }, - "extensionDescription": { - "message": "ویکیپدیا، دانشنامهٔ آزاد" - }, - "searchUrl": { - "message": "https://fa.wikipedia.org/wiki/%D9%88%DB%8C%DA%98%D9%87:%D8%AC%D8%B3%D8%AA%D8..." - }, - "searchForm": { - "message": "https://fa.wikipedia.org/wiki/%D9%88%DB%8C%DA%98%D9%87:%D8%AC%D8%B3%D8%AA%D8..." - }, - "suggestUrl": { - "message": "https://fa.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/fi/messages.json b/browser/components/search/extensions/wikipedia/_locales/fi/messages.json deleted file mode 100644 index 17a9cbe22c421..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/fi/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (fi)" - }, - "extensionDescription": { - "message": "Wikipedia (fi), vapaa tietosanakirja" - }, - "searchUrl": { - "message": "https://fi.wikipedia.org/wiki/Toiminnot:Haku" - }, - "searchForm": { - "message": "https://fi.wikipedia.org/wiki/Toiminnot:Haku?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://fi.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/fr/messages.json b/browser/components/search/extensions/wikipedia/_locales/fr/messages.json deleted file mode 100644 index 33dcbe9dc502f..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/fr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipédia (fr)" - }, - "extensionDescription": { - "message": "Wikipédia, l'encyclopédie libre" - }, - "searchUrl": { - "message": "https://fr.wikipedia.org/wiki/Sp%C3%A9cial:Recherche" - }, - "searchForm": { - "message": "https://fr.wikipedia.org/wiki/Sp%C3%A9cial:Recherche?search=%7BsearchTerms%7..." - }, - "suggestUrl": { - "message": "https://fr.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/fy-NL/messages.json b/browser/components/search/extensions/wikipedia/_locales/fy-NL/messages.json deleted file mode 100644 index f350162fbbaf9..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/fy-NL/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedy (fy)" - }, - "extensionDescription": { - "message": "De fergese ensyklopedy" - }, - "searchUrl": { - "message": "https://fy.wikipedia.org/wiki/Wiki:Sykje" - }, - "searchForm": { - "message": "https://fy.wikipedia.org/wiki/Wiki:Sykje?search=%7BsearchTerms%7D&source..." - }, - "suggestUrl": { - "message": "https://fy.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ga-IE/messages.json b/browser/components/search/extensions/wikipedia/_locales/ga-IE/messages.json deleted file mode 100644 index 994ea723c6dad..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ga-IE/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vicipéid (ga)" - }, - "extensionDescription": { - "message": "Vicipéid, an Chiclipéid Shaor" - }, - "searchUrl": { - "message": "https://ga.wikipedia.org/wiki/Speisialta:Search" - }, - "searchForm": { - "message": "https://ga.wikipedia.org/wiki/Speisialta:Search?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://ga.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/gd/messages.json b/browser/components/search/extensions/wikipedia/_locales/gd/messages.json deleted file mode 100644 index f16f16fb4a02a..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/gd/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Uicipeid (gd)" - }, - "extensionDescription": { - "message": "Wikipedia, An leabhar mòr-eòlais" - }, - "searchUrl": { - "message": "https://gd.wikipedia.org/wiki/S%C3%B2nraichte:Search" - }, - "searchForm": { - "message": "https://gd.wikipedia.org/wiki/S%C3%B2nraichte:Search?search=%7BsearchTerms%7..." - }, - "suggestUrl": { - "message": "https://gd.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/gl/messages.json b/browser/components/search/extensions/wikipedia/_locales/gl/messages.json deleted file mode 100644 index 88880bffc3d94..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/gl/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (gl)" - }, - "extensionDescription": { - "message": "Wikipedia, a enciclopedia libre" - }, - "searchUrl": { - "message": "https://gl.wikipedia.org/wiki/Especial:Procurar" - }, - "searchForm": { - "message": "https://gl.wikipedia.org/wiki/Especial:Procurar?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://gl.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/gn/messages.json b/browser/components/search/extensions/wikipedia/_locales/gn/messages.json deleted file mode 100644 index 5efc5ed74a95d..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/gn/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipetã (gn)" - }, - "extensionDescription": { - "message": "Vikipetã, opaite tembikuaa hekosãsóva renda" - }, - "searchUrl": { - "message": "https://gn.wikipedia.org/wiki/Mba%27ech%C4%A9ch%C4%A9:Buscar" - }, - "searchForm": { - "message": "https://gn.wikipedia.org/wiki/Mba%27ech%C4%A9ch%C4%A9:Buscar?search=%7Bsearc..." - }, - "suggestUrl": { - "message": "https://gn.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/gu/messages.json b/browser/components/search/extensions/wikipedia/_locales/gu/messages.json deleted file mode 100644 index 3d2f68826fc57..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/gu/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "વિકિપીડિયા (gu)" - }, - "extensionDescription": { - "message": "વીકીપીડિયા, મુક્ત એનસાયક્લોપીડિયા" - }, - "searchUrl": { - "message": "https://gu.wikipedia.org/wiki/%E0%AA%B5%E0%AA%BF%E0%AA%B6%E0%AB%87%E0%AA%B7:..." - }, - "searchForm": { - "message": "https://gu.wikipedia.org/wiki/%E0%AA%B5%E0%AA%BF%E0%AA%B6%E0%AB%87%E0%AA%B7:..." - }, - "suggestUrl": { - "message": "https://gu.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/he/messages.json b/browser/components/search/extensions/wikipedia/_locales/he/messages.json deleted file mode 100644 index 1f8471e980f0a..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/he/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "ויקיפדיה" - }, - "extensionDescription": { - "message": "ויקיפדיה" - }, - "searchUrl": { - "message": "https://he.wikipedia.org/wiki/%D7%9E%D7%99%D7%95%D7%97%D7%93:%D7%97%D7%99%D7..." - }, - "searchForm": { - "message": "https://he.wikipedia.org/wiki/%D7%9E%D7%99%D7%95%D7%97%D7%93:%D7%97%D7%99%D7..." - }, - "suggestUrl": { - "message": "https://he.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/hi/messages.json b/browser/components/search/extensions/wikipedia/_locales/hi/messages.json deleted file mode 100644 index f3b7d14eafa02..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/hi/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "विकिपीडिया (hi)" - }, - "extensionDescription": { - "message": "विकिपीडिया (हिन्दी)" - }, - "searchUrl": { - "message": "https://hi.wikipedia.org/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:..." - }, - "searchForm": { - "message": "https://hi.wikipedia.org/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:..." - }, - "suggestUrl": { - "message": "https://hi.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/hr/messages.json b/browser/components/search/extensions/wikipedia/_locales/hr/messages.json deleted file mode 100644 index 18a6177efccaf..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/hr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedija (hr)" - }, - "extensionDescription": { - "message": "Wikipedija, slobodna enciklopedija" - }, - "searchUrl": { - "message": "https://hr.wikipedia.org/wiki/Posebno:Tra%C5%BEi" - }, - "searchForm": { - "message": "https://hr.wikipedia.org/wiki/Posebno:Tra%C5%BEi?search=%7BsearchTerms%7D&am..." - }, - "suggestUrl": { - "message": "https://hr.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/hsb/messages.json b/browser/components/search/extensions/wikipedia/_locales/hsb/messages.json deleted file mode 100644 index d4e62836e6e94..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/hsb/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedija (hsb)" - }, - "extensionDescription": { - "message": "Wikipedija, swobodna encyklopedija" - }, - "searchUrl": { - "message": "https://hsb.wikipedia.org/wiki/Specialnje:Pyta%C4%87" - }, - "searchForm": { - "message": "https://hsb.wikipedia.org/wiki/Specialnje:Pyta%C4%87?search=%7BsearchTerms%7..." - }, - "suggestUrl": { - "message": "https://hsb.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTe..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/hu/messages.json b/browser/components/search/extensions/wikipedia/_locales/hu/messages.json deleted file mode 100644 index 68300c48a6f3d..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/hu/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipédia (hu)" - }, - "extensionDescription": { - "message": "Wikipedia, a szabad enciklopédia" - }, - "searchUrl": { - "message": "https://hu.wikipedia.org/wiki/Speci%C3%A1lis:Keres%C3%A9s" - }, - "searchForm": { - "message": "https://hu.wikipedia.org/wiki/Speci%C3%A1lis:Keres%C3%A9s?search=%7BsearchTe..." - }, - "suggestUrl": { - "message": "https://hu.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/hy/messages.json b/browser/components/search/extensions/wikipedia/_locales/hy/messages.json deleted file mode 100644 index 56c2ae2c641b7..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/hy/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (hy)" - }, - "extensionDescription": { - "message": "Վիքիփեդիա՝ ազատ հանրագիտարան" - }, - "searchUrl": { - "message": "https://hy.wikipedia.org/wiki/%D5%8D%D5%BA%D5%A1%D5%BD%D5%A1%D6%80%D5%AF%D5%..." - }, - "searchForm": { - "message": "https://hy.wikipedia.org/wiki/%D5%8D%D5%BA%D5%A1%D5%BD%D5%A1%D6%80%D5%AF%D5%..." - }, - "suggestUrl": { - "message": "https://hy.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ia/messages.json b/browser/components/search/extensions/wikipedia/_locales/ia/messages.json deleted file mode 100644 index 6d997ae8fc816..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ia/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (ia)" - }, - "extensionDescription": { - "message": "Wikipedia, le encyclopedia libere" - }, - "searchUrl": { - "message": "https://ia.wikipedia.org/wiki/Special:Recerca" - }, - "searchForm": { - "message": "https://ia.wikipedia.org/wiki/Special:Recerca?search=%7BsearchTerms%7D&s..." - }, - "suggestUrl": { - "message": "https://ia.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/id/messages.json b/browser/components/search/extensions/wikipedia/_locales/id/messages.json deleted file mode 100644 index 1d35e71b956d4..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/id/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (id)" - }, - "extensionDescription": { - "message": "Wikipedia, ensiklopedia bebas" - }, - "searchUrl": { - "message": "https://id.wikipedia.org/wiki/Istimewa:Pencarian" - }, - "searchForm": { - "message": "https://id.wikipedia.org/wiki/Istimewa:Pencarian?search=%7BsearchTerms%7D&am..." - }, - "suggestUrl": { - "message": "https://id.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/is/messages.json b/browser/components/search/extensions/wikipedia/_locales/is/messages.json deleted file mode 100644 index f722d88187de2..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/is/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (is)" - }, - "extensionDescription": { - "message": "Wikipedia, the free encyclopedia" - }, - "searchUrl": { - "message": "https://is.wikipedia.org/wiki/Kerfiss%C3%AD%C3%B0a:Leit" - }, - "searchForm": { - "message": "https://is.wikipedia.org/wiki/Kerfiss%C3%AD%C3%B0a:Leit?search=%7BsearchTerm..." - }, - "suggestUrl": { - "message": "https://is.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/it/messages.json b/browser/components/search/extensions/wikipedia/_locales/it/messages.json deleted file mode 100644 index 2ca645740f874..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/it/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (it)" - }, - "extensionDescription": { - "message": "Wikipedia, l'enciclopedia libera" - }, - "searchUrl": { - "message": "https://it.wikipedia.org/wiki/Speciale:Ricerca" - }, - "searchForm": { - "message": "https://it.wikipedia.org/wiki/Speciale:Ricerca?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://it.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ja/messages.json b/browser/components/search/extensions/wikipedia/_locales/ja/messages.json deleted file mode 100644 index 7215e68768f0b..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ja/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (ja)" - }, - "extensionDescription": { - "message": "Wikipedia - フリー百科事典" - }, - "searchUrl": { - "message": "https://ja.wikipedia.org/wiki/%E7%89%B9%E5%88%A5:%E6%A4%9C%E7%B4%A2" - }, - "searchForm": { - "message": "https://ja.wikipedia.org/wiki/%E7%89%B9%E5%88%A5:%E6%A4%9C%E7%B4%A2?search=%..." - }, - "suggestUrl": { - "message": "https://ja.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ka/messages.json b/browser/components/search/extensions/wikipedia/_locales/ka/messages.json deleted file mode 100644 index c460a093e5e4c..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ka/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "ვიკიპედია (ka)" - }, - "extensionDescription": { - "message": "ვიკიპედია, თავისუფალი ენციკლოპედია" - }, - "searchUrl": { - "message": "https://ka.wikipedia.org/wiki/%E1%83%A1%E1%83%9E%E1%83%94%E1%83%AA%E1%83%98%..." - }, - "searchForm": { - "message": "https://ka.wikipedia.org/wiki/%E1%83%A1%E1%83%9E%E1%83%94%E1%83%AA%E1%83%98%..." - }, - "suggestUrl": { - "message": "https://ka.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/kab/messages.json b/browser/components/search/extensions/wikipedia/_locales/kab/messages.json deleted file mode 100644 index 3cf743b616fe5..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/kab/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (kab)" - }, - "extensionDescription": { - "message": "Wikipedia, tasanayt tilellit" - }, - "searchUrl": { - "message": "https://kab.wikipedia.org/wiki/Uslig:Search" - }, - "searchForm": { - "message": "https://kab.wikipedia.org/wiki/Uslig:Search?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://kab.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTe..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/kk/messages.json b/browser/components/search/extensions/wikipedia/_locales/kk/messages.json deleted file mode 100644 index 0844cca0d7e18..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/kk/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Уикипедия (kk)" - }, - "extensionDescription": { - "message": "Уикипедия (kk)" - }, - "searchUrl": { - "message": "https://kk.wikipedia.org/wiki/%D0%90%D1%80%D0%BD%D0%B0%D0%B9%D1%8B:%D0%86%D0..." - }, - "searchForm": { - "message": "https://kk.wikipedia.org/wiki/%D0%90%D1%80%D0%BD%D0%B0%D0%B9%D1%8B:%D0%86%D0..." - }, - "suggestUrl": { - "message": "https://kk.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/km/messages.json b/browser/components/search/extensions/wikipedia/_locales/km/messages.json deleted file mode 100644 index 0f0a0880e188a..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/km/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "វីគីភីឌា (km)" - }, - "extensionDescription": { - "message": "វីគីភីឌា សព្វវចនាធិប្បាយសេរី" - }, - "searchUrl": { - "message": "https://km.wikipedia.org/wiki/%E1%9E%96%E1%9E%B7%E1%9E%9F%E1%9F%81%E1%9E%9F:..." - }, - "searchForm": { - "message": "https://km.wikipedia.org/wiki/%E1%9E%96%E1%9E%B7%E1%9E%9F%E1%9F%81%E1%9E%9F:..." - }, - "suggestUrl": { - "message": "https://km.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/kn/messages.json b/browser/components/search/extensions/wikipedia/_locales/kn/messages.json deleted file mode 100644 index 379ef20085a35..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/kn/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (kn)" - }, - "extensionDescription": { - "message": "Wikipedia, the free encyclopedia" - }, - "searchUrl": { - "message": "https://kn.wikipedia.org/wiki/%E0%B2%B5%E0%B2%BF%E0%B2%B6%E0%B3%87%E0%B2%B7:..." - }, - "searchForm": { - "message": "https://kn.wikipedia.org/wiki/%E0%B2%B5%E0%B2%BF%E0%B2%B6%E0%B3%87%E0%B2%B7:..." - }, - "suggestUrl": { - "message": "https://kn.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/kr/messages.json b/browser/components/search/extensions/wikipedia/_locales/kr/messages.json deleted file mode 100644 index 54296cac62bd7..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/kr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "위키백과 (ko)" - }, - "extensionDescription": { - "message": "Wikipedia, the free encyclopedia" - }, - "searchUrl": { - "message": "https://ko.wikipedia.org/wiki/%ED%8A%B9%EC%88%98%EA%B8%B0%EB%8A%A5:%EC%B0%BE..." - }, - "searchForm": { - "message": "https://ko.wikipedia.org/wiki/%ED%8A%B9%EC%88%98%EA%B8%B0%EB%8A%A5:%EC%B0%BE..." - }, - "suggestUrl": { - "message": "https://ko.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/lij/messages.json b/browser/components/search/extensions/wikipedia/_locales/lij/messages.json deleted file mode 100644 index cb90db5e40995..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/lij/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (lij)" - }, - "extensionDescription": { - "message": "Wikipedia, l'enciclopedia libera" - }, - "searchUrl": { - "message": "https://lij.wikipedia.org/wiki/Spe%C3%A7iale:Ri%C3%A7erca" - }, - "searchForm": { - "message": "https://lij.wikipedia.org/wiki/Spe%C3%A7iale:Ri%C3%A7erca?search=%7BsearchTe..." - }, - "suggestUrl": { - "message": "https://lij.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTe..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/lo/messages.json b/browser/components/search/extensions/wikipedia/_locales/lo/messages.json deleted file mode 100644 index 712746ec63169..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/lo/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "ວິກິພີເດຍ (lo)" - }, - "extensionDescription": { - "message": "ວິກິພີເດຍ, ສາລານຸກົມເສລີ" - }, - "searchUrl": { - "message": "https://lo.wikipedia.org/wiki/%E0%BA%9E%E0%BA%B4%E0%BB%80%E0%BA%AA%E0%BA%94:..." - }, - "searchForm": { - "message": "https://lo.wikipedia.org/wiki/%E0%BA%9E%E0%BA%B4%E0%BB%80%E0%BA%AA%E0%BA%94:..." - }, - "suggestUrl": { - "message": "https://lo.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/lt/messages.json b/browser/components/search/extensions/wikipedia/_locales/lt/messages.json deleted file mode 100644 index c061bcc5224c0..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/lt/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (lt)" - }, - "extensionDescription": { - "message": "Vikipedija, laisvoji enciklopedija" - }, - "searchUrl": { - "message": "https://lt.wikipedia.org/wiki/Specialus:Paie%C5%A1ka" - }, - "searchForm": { - "message": "https://lt.wikipedia.org/wiki/Specialus:Paie%C5%A1ka?search=%7BsearchTerms%7..." - }, - "suggestUrl": { - "message": "https://lt.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ltg/messages.json b/browser/components/search/extensions/wikipedia/_locales/ltg/messages.json deleted file mode 100644 index 0e02810ef3bfa..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ltg/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipedeja (ltg)" - }, - "extensionDescription": { - "message": "Vikipēdija, breivuo eņciklopedeja" - }, - "searchUrl": { - "message": "https://ltg.wikipedia.org/wiki/Sevi%C5%A1kuo:Search" - }, - "searchForm": { - "message": "https://ltg.wikipedia.org/wiki/Sevi%C5%A1kuo:Search?search=%7BsearchTerms%7D..." - }, - "suggestUrl": { - "message": "https://ltg.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTe..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/lv/messages.json b/browser/components/search/extensions/wikipedia/_locales/lv/messages.json deleted file mode 100644 index f73814b8574f7..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/lv/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipēdija" - }, - "extensionDescription": { - "message": "Vikipēdija, brīvā enciklopēdija" - }, - "searchUrl": { - "message": "https://lv.wikipedia.org/wiki/Special:Search" - }, - "searchForm": { - "message": "https://lv.wikipedia.org/wiki/Special:Search?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://lv.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/mk/messages.json b/browser/components/search/extensions/wikipedia/_locales/mk/messages.json deleted file mode 100644 index de7e06e1ac4a6..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/mk/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Википедија (mk)" - }, - "extensionDescription": { - "message": "Википедија, слободната енциклопедија" - }, - "searchUrl": { - "message": "https://mk.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B5%D1%86%D0%B8%D1%98%D0%B0%D0%..." - }, - "searchForm": { - "message": "https://mk.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B5%D1%86%D0%B8%D1%98%D0%B0%D0%..." - }, - "suggestUrl": { - "message": "https://mk.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/mr/messages.json b/browser/components/search/extensions/wikipedia/_locales/mr/messages.json deleted file mode 100644 index bd46dd83700c1..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/mr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "विकिपीडिया (mr)" - }, - "extensionDescription": { - "message": "विकिपीडिया, मोफत माहितीकोष" - }, - "searchUrl": { - "message": "https://mr.wikipedia.org/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:..." - }, - "searchForm": { - "message": "https://mr.wikipedia.org/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:..." - }, - "suggestUrl": { - "message": "https://mr.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ms/messages.json b/browser/components/search/extensions/wikipedia/_locales/ms/messages.json deleted file mode 100644 index c817e82c78216..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ms/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (ms)" - }, - "extensionDescription": { - "message": "Wikipedia, ensiklopedia bebas" - }, - "searchUrl": { - "message": "https://ms.wikipedia.org/wiki/Khas:Gelintar" - }, - "searchForm": { - "message": "https://ms.wikipedia.org/wiki/Khas:Gelintar?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://ms.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/my/messages.json b/browser/components/search/extensions/wikipedia/_locales/my/messages.json deleted file mode 100644 index 62342d1b90ae0..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/my/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (my)" - }, - "extensionDescription": { - "message": "အခမဲ့လွတ်လပ်စွယ်စုံကျမ်း" - }, - "searchUrl": { - "message": "https://my.wikipedia.org/wiki/Special:Search" - }, - "searchForm": { - "message": "https://my.wikipedia.org/wiki/Special:Search?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://my.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ne/messages.json b/browser/components/search/extensions/wikipedia/_locales/ne/messages.json deleted file mode 100644 index eb22344341e40..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ne/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "विकिपीडिया (ne)" - }, - "extensionDescription": { - "message": "विकिपिडिया एक स्वतन्त्र विश्वकोष" - }, - "searchUrl": { - "message": "https://ne.wikipedia.org/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:..." - }, - "searchForm": { - "message": "https://ne.wikipedia.org/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:..." - }, - "suggestUrl": { - "message": "https://ne.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/nl/messages.json b/browser/components/search/extensions/wikipedia/_locales/nl/messages.json deleted file mode 100644 index c2a810c2ae304..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/nl/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (nl)" - }, - "extensionDescription": { - "message": "De vrije encyclopedie" - }, - "searchUrl": { - "message": "https://nl.wikipedia.org/wiki/Speciaal:Zoeken" - }, - "searchForm": { - "message": "https://nl.wikipedia.org/wiki/Speciaal:Zoeken?search=%7BsearchTerms%7D&s..." - }, - "suggestUrl": { - "message": "https://nl.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/oc/messages.json b/browser/components/search/extensions/wikipedia/_locales/oc/messages.json deleted file mode 100644 index 3cadc3d68f074..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/oc/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipèdia (oc)" - }, - "extensionDescription": { - "message": "Wikipèdia, l'enciclopèdia liura" - }, - "searchUrl": { - "message": "https://oc.wikipedia.org/wiki/Especial:Rec%C3%A8rca" - }, - "searchForm": { - "message": "https://oc.wikipedia.org/wiki/Especial:Rec%C3%A8rca?search=%7BsearchTerms%7D..." - }, - "suggestUrl": { - "message": "https://oc.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/pa/messages.json b/browser/components/search/extensions/wikipedia/_locales/pa/messages.json deleted file mode 100644 index dff38c2146fdf..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/pa/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (pa)" - }, - "extensionDescription": { - "message": "ਵਿਕਿਪੀਡਿਆ, ਮੁਫ਼ਤ/ਮੁਕਤ ਸ਼ਬਦਕੋਸ਼" - }, - "searchUrl": { - "message": "https://pa.wikipedia.org/wiki/%E0%A8%96%E0%A8%BC%E0%A8%BE%E0%A8%B8:%E0%A8%96..." - }, - "searchForm": { - "message": "https://pa.wikipedia.org/wiki/%E0%A8%96%E0%A8%BC%E0%A8%BE%E0%A8%B8:%E0%A8%96..." - }, - "suggestUrl": { - "message": "https://pa.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/pl/messages.json b/browser/components/search/extensions/wikipedia/_locales/pl/messages.json deleted file mode 100644 index 315aa0d9cbe10..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/pl/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (pl)" - }, - "extensionDescription": { - "message": "Wikipedia, wolna encyklopedia" - }, - "searchUrl": { - "message": "https://pl.wikipedia.org/wiki/Specjalna:Szukaj" - }, - "searchForm": { - "message": "https://pl.wikipedia.org/wiki/Specjalna:Szukaj?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://pl.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/pt/messages.json b/browser/components/search/extensions/wikipedia/_locales/pt/messages.json deleted file mode 100644 index 4beaa97acc887..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/pt/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (pt)" - }, - "extensionDescription": { - "message": "Wikipédia, a enciclopédia livre" - }, - "searchUrl": { - "message": "https://pt.wikipedia.org/wiki/Especial:Pesquisar" - }, - "searchForm": { - "message": "https://pt.wikipedia.org/wiki/Especial:Pesquisar?search=%7BsearchTerms%7D&am..." - }, - "suggestUrl": { - "message": "https://pt.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/rm/messages.json b/browser/components/search/extensions/wikipedia/_locales/rm/messages.json deleted file mode 100644 index 8258d5e434516..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/rm/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (rm)" - }, - "extensionDescription": { - "message": "Vichipedia, l'enciclopedia libra" - }, - "searchUrl": { - "message": "https://rm.wikipedia.org/wiki/Spezial:Search" - }, - "searchForm": { - "message": "https://rm.wikipedia.org/wiki/Spezial:Search?search=%7BsearchTerms%7D&so..." - }, - "suggestUrl": { - "message": "https://rm.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ro/messages.json b/browser/components/search/extensions/wikipedia/_locales/ro/messages.json deleted file mode 100644 index 48865fd547e44..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ro/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (ro)" - }, - "extensionDescription": { - "message": "Wikipedia, enciclopedia liberă" - }, - "searchUrl": { - "message": "https://ro.wikipedia.org/wiki/Special:C%C4%83utare" - }, - "searchForm": { - "message": "https://ro.wikipedia.org/wiki/Special:C%C4%83utare?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://ro.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ru/messages.json b/browser/components/search/extensions/wikipedia/_locales/ru/messages.json deleted file mode 100644 index 569467691d7c6..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ru/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Википедия (ru)" - }, - "extensionDescription": { - "message": "Википедия, свободная энциклопедия" - }, - "searchUrl": { - "message": "https://ru.wikipedia.org/wiki/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%..." - }, - "searchForm": { - "message": "https://ru.wikipedia.org/wiki/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%..." - }, - "suggestUrl": { - "message": "https://ru.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/si/messages.json b/browser/components/search/extensions/wikipedia/_locales/si/messages.json deleted file mode 100644 index 0406ae728d716..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/si/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (si)" - }, - "extensionDescription": { - "message": "Wikipedia, the free encyclopedia" - }, - "searchUrl": { - "message": "https://si.wikipedia.org/wiki/%E0%B7%80%E0%B7%92%E0%B7%81%E0%B7%9A%E0%B7%82:..." - }, - "searchForm": { - "message": "https://si.wikipedia.org/wiki/%E0%B7%80%E0%B7%92%E0%B7%81%E0%B7%9A%E0%B7%82:..." - }, - "suggestUrl": { - "message": "https://si.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/sk/messages.json b/browser/components/search/extensions/wikipedia/_locales/sk/messages.json deleted file mode 100644 index 5c2f75f8b031e..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/sk/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipédia (sk)" - }, - "extensionDescription": { - "message": "Wikipédia, slobodná a otvorená encyklopédia" - }, - "searchUrl": { - "message": "https://sk.wikipedia.org/wiki/%C5%A0peci%C3%A1lne:H%C4%BEadanie" - }, - "searchForm": { - "message": "https://sk.wikipedia.org/wiki/%C5%A0peci%C3%A1lne:H%C4%BEadanie?search=%7Bse..." - }, - "suggestUrl": { - "message": "https://sk.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/sl/messages.json b/browser/components/search/extensions/wikipedia/_locales/sl/messages.json deleted file mode 100644 index 7385a22034745..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/sl/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedija (sl)" - }, - "extensionDescription": { - "message": "Wikipedija, prosta enciklopedija" - }, - "searchUrl": { - "message": "https://sl.wikipedia.org/wiki/Posebno:Iskanje" - }, - "searchForm": { - "message": "https://sl.wikipedia.org/wiki/Posebno:Iskanje?search=%7BsearchTerms%7D&s..." - }, - "suggestUrl": { - "message": "https://sl.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/sq/messages.json b/browser/components/search/extensions/wikipedia/_locales/sq/messages.json deleted file mode 100644 index 68361d8ab2941..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/sq/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (sq)" - }, - "extensionDescription": { - "message": "Wikipedia, enciklopedia e lirë" - }, - "searchUrl": { - "message": "https://sq.wikipedia.org/wiki/Speciale:K%C3%ABrkim" - }, - "searchForm": { - "message": "https://sq.wikipedia.org/wiki/Speciale:K%C3%ABrkim?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://sq.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/sr/messages.json b/browser/components/search/extensions/wikipedia/_locales/sr/messages.json deleted file mode 100644 index 50ebc0a197a1e..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/sr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Википедија (sr)" - }, - "extensionDescription": { - "message": "Претрага Википедије на српском језику" - }, - "searchUrl": { - "message": "https://sr.wikipedia.org/wiki/%D0%9F%D0%BE%D1%81%D0%B5%D0%B1%D0%BD%D0%BE:%D0..." - }, - "searchForm": { - "message": "https://sr.wikipedia.org/wiki/%D0%9F%D0%BE%D1%81%D0%B5%D0%B1%D0%BD%D0%BE:%D0..." - }, - "suggestUrl": { - "message": "https://sr.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/sv-SE/messages.json b/browser/components/search/extensions/wikipedia/_locales/sv-SE/messages.json deleted file mode 100644 index 1edc3db80d98c..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/sv-SE/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (sv)" - }, - "extensionDescription": { - "message": "Wikipedia, den fria encyklopedin" - }, - "searchUrl": { - "message": "https://sv.wikipedia.org/wiki/Special:S%C3%B6k" - }, - "searchForm": { - "message": "https://sv.wikipedia.org/wiki/Special:S%C3%B6k?search=%7BsearchTerms%7D&..." - }, - "suggestUrl": { - "message": "https://sv.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ta/messages.json b/browser/components/search/extensions/wikipedia/_locales/ta/messages.json deleted file mode 100644 index 54397603b028a..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ta/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "விக்கிப்பீடியா (ta)" - }, - "extensionDescription": { - "message": "விக்கிப்பீடியா (ta)" - }, - "searchUrl": { - "message": "https://ta.wikipedia.org/wiki/%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%AA%E0%AF%8D%..." - }, - "searchForm": { - "message": "https://ta.wikipedia.org/wiki/%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%AA%E0%AF%8D%..." - }, - "suggestUrl": { - "message": "https://ta.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/te/messages.json b/browser/components/search/extensions/wikipedia/_locales/te/messages.json deleted file mode 100644 index c474be12a76f9..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/te/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "వికీపీడియా (te)" - }, - "extensionDescription": { - "message": "వికీపీడియా (te)" - }, - "searchUrl": { - "message": "https://te.wikipedia.org/wiki/%E0%B0%AA%E0%B1%8D%E0%B0%B0%E0%B0%A4%E0%B1%8D%..." - }, - "searchForm": { - "message": "https://te.wikipedia.org/wiki/%E0%B0%AA%E0%B1%8D%E0%B0%B0%E0%B0%A4%E0%B1%8D%..." - }, - "suggestUrl": { - "message": "https://te.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/th/messages.json b/browser/components/search/extensions/wikipedia/_locales/th/messages.json deleted file mode 100644 index 3d6aeb07ca2cf..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/th/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "วิกิพีเดีย" - }, - "extensionDescription": { - "message": "วิกิพีเดีย สารานุกรมเสรี" - }, - "searchUrl": { - "message": "https://th.wikipedia.org/wiki/%E0%B8%9E%E0%B8%B4%E0%B9%80%E0%B8%A8%E0%B8%A9:..." - }, - "searchForm": { - "message": "https://th.wikipedia.org/wiki/%E0%B8%9E%E0%B8%B4%E0%B9%80%E0%B8%A8%E0%B8%A9:..." - }, - "suggestUrl": { - "message": "https://th.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/tl/messages.json b/browser/components/search/extensions/wikipedia/_locales/tl/messages.json deleted file mode 100644 index d55b03131f97c..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/tl/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (tl)" - }, - "extensionDescription": { - "message": "Wikipedia, ang malayang ensiklopedya" - }, - "searchUrl": { - "message": "https://tl.wikipedia.org/wiki/Natatangi:Maghanap" - }, - "searchForm": { - "message": "https://tl.wikipedia.org/wiki/Natatangi:Maghanap?search=%7BsearchTerms%7D&am..." - }, - "suggestUrl": { - "message": "https://tl.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/tr/messages.json b/browser/components/search/extensions/wikipedia/_locales/tr/messages.json deleted file mode 100644 index 878b28ab68b24..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/tr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (tr)" - }, - "extensionDescription": { - "message": "Vikipedi, özgür ansiklopedi" - }, - "searchUrl": { - "message": "https://tr.wikipedia.org/wiki/%C3%96zel:Ara" - }, - "searchForm": { - "message": "https://tr.wikipedia.org/wiki/%C3%96zel:Ara?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://tr.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/uk/messages.json b/browser/components/search/extensions/wikipedia/_locales/uk/messages.json deleted file mode 100644 index 2749b86304bfa..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/uk/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Вікіпедія (uk)" - }, - "extensionDescription": { - "message": "Вікіпедія, вільна енциклопедія" - }, - "searchUrl": { - "message": "https://uk.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B5%D1%86%D1%96%D0%B0%D0%BB%D1%..." - }, - "searchForm": { - "message": "https://uk.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B5%D1%86%D1%96%D0%B0%D0%BB%D1%..." - }, - "suggestUrl": { - "message": "https://uk.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/ur/messages.json b/browser/components/search/extensions/wikipedia/_locales/ur/messages.json deleted file mode 100644 index dcc87e0c853cc..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/ur/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "ویکیپیڈیا (ur)" - }, - "extensionDescription": { - "message": "ویکیپیڈیا آزاد دائرۃ المعارف" - }, - "searchUrl": { - "message": "https://ur.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%AA%D9%84%D8%A7%D8%B4" - }, - "searchForm": { - "message": "https://ur.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%AA%D9%84%D8%A7%D8%B4?se..." - }, - "suggestUrl": { - "message": "https://ur.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/uz/messages.json b/browser/components/search/extensions/wikipedia/_locales/uz/messages.json deleted file mode 100644 index 89a8f2a89bca5..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/uz/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Vikipediya (uz)" - }, - "extensionDescription": { - "message": "Vikipediya, ochiq ensiklopediya" - }, - "searchUrl": { - "message": "https://uz.wikipedia.org/wiki/Maxsus:Search" - }, - "searchForm": { - "message": "https://uz.wikipedia.org/wiki/Maxsus:Search?search=%7BsearchTerms%7D&sou..." - }, - "suggestUrl": { - "message": "https://uz.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/vi/messages.json b/browser/components/search/extensions/wikipedia/_locales/vi/messages.json deleted file mode 100644 index c0844e4feb144..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/vi/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (vi)" - }, - "extensionDescription": { - "message": "Wikipedia, bách khoa toàn thư mở" - }, - "searchUrl": { - "message": "https://vi.wikipedia.org/wiki/%C4%90%E1%BA%B7c_bi%E1%BB%87t:T%C3%ACm_ki%E1%B..." - }, - "searchForm": { - "message": "https://vi.wikipedia.org/wiki/%C4%90%E1%BA%B7c_bi%E1%BB%87t:T%C3%ACm_ki%E1%B..." - }, - "suggestUrl": { - "message": "https://vi.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/wo/messages.json b/browser/components/search/extensions/wikipedia/_locales/wo/messages.json deleted file mode 100644 index 7efde3b1d0f47..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/wo/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (wo)" - }, - "extensionDescription": { - "message": "Wikipedia, Jimbulang bu Ubbeeku bi" - }, - "searchUrl": { - "message": "https://wo.wikipedia.org/wiki/Jagleel:Ceet" - }, - "searchForm": { - "message": "https://wo.wikipedia.org/wiki/Jagleel:Ceet?search=%7BsearchTerms%7D&sour..." - }, - "suggestUrl": { - "message": "https://wo.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/zh-CN/messages.json b/browser/components/search/extensions/wikipedia/_locales/zh-CN/messages.json deleted file mode 100644 index 29047565a2431..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/zh-CN/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "维基百科" - }, - "extensionDescription": { - "message": "维基百科,自由的百科全书" - }, - "searchUrl": { - "message": "https://zh.wikipedia.org/wiki/Special:%E6%90%9C%E7%B4%A2" - }, - "searchForm": { - "message": "https://zh.wikipedia.org/wiki/Special:%E6%90%9C%E7%B4%A2?search=%7BsearchTer..." - }, - "suggestUrl": { - "message": "https://zh.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/_locales/zh-TW/messages.json b/browser/components/search/extensions/wikipedia/_locales/zh-TW/messages.json deleted file mode 100644 index a0e8d880ea267..0000000000000 --- a/browser/components/search/extensions/wikipedia/_locales/zh-TW/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Wikipedia (zh)" - }, - "extensionDescription": { - "message": "維基百科,自由的百科全書" - }, - "searchUrl": { - "message": "https://zh.wikipedia.org/wiki/Special:%E6%90%9C%E7%B4%A2" - }, - "searchForm": { - "message": "https://zh.wikipedia.org/wiki/Special:%E6%90%9C%E7%B4%A2?search=%7BsearchTer..." - }, - "suggestUrl": { - "message": "https://zh.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer..." - }, - "searchUrlGetParams": { - "message": "search={searchTerms}&sourceid=Mozilla-search&variant=zh-tw" - } -} \ No newline at end of file diff --git a/browser/components/search/extensions/wikipedia/manifest.json b/browser/components/search/extensions/wikipedia/manifest.json index cd95d3aa9e1c1..0af3b2a1c2118 100644 --- a/browser/components/search/extensions/wikipedia/manifest.json +++ b/browser/components/search/extensions/wikipedia/manifest.json @@ -1,6 +1,6 @@ { - "name": "__MSG_extensionName__", - "description": "__MSG_extensionDescription__", + "name": "Wikipedia (en)", + "description": "Wikipedia, the Free Encyclopedia", "manifest_version": 2, "version": "1.1", "applications": { @@ -9,7 +9,6 @@ } }, "hidden": true, - "default_locale": "en", "icons": { "16": "favicon.ico" }, @@ -19,11 +18,11 @@ "chrome_settings_overrides": { "search_provider": { "keyword": "@wikipedia", - "name": "__MSG_extensionName__", - "search_url": "__MSG_searchUrl__", - "search_form": "__MSG_searchForm__", - "suggest_url": "__MSG_suggestUrl__", - "search_url_get_params": "__MSG_searchUrlGetParams__" + "name": "Wikipedia (en)", + "search_url": "https://en.wikipedia.org/wiki/Special:Search", + "search_form": "https://en.wikipedia.org/wiki/Special:Search?search=%7BsearchTerms%7D&so...", + "suggest_url": "https://en.wikipedia.org/w/api.php?action=opensearch&search=%7BsearchTer...", + "search_url_get_params": "search={searchTerms}&sourceid=Mozilla-search" } } } diff --git a/browser/components/search/extensions/yahoo/favicon.ico b/browser/components/search/extensions/yahoo/favicon.ico new file mode 100644 index 0000000000000..9bd1d9f7c008c Binary files /dev/null and b/browser/components/search/extensions/yahoo/favicon.ico differ diff --git a/browser/components/search/extensions/yahoo/manifest.json b/browser/components/search/extensions/yahoo/manifest.json new file mode 100644 index 0000000000000..e1f04a373c2ea --- /dev/null +++ b/browser/components/search/extensions/yahoo/manifest.json @@ -0,0 +1,28 @@ +{ + "name": "Yahoo", + "description": "Yahoo Search", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "yahoo@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.ico" + }, + "web_accessible_resources": [ + "favicon.ico" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "Yahoo", + "search_url": "https://search.yahoo.com/yhs/search", + "search_form": "https://search.yahoo.com/yhs/search?p=%7BsearchTerms%7D&ei=UTF-8&hsp...", + "search_url_get_params": "p={searchTerms}&ei=UTF-8&hspart=mozilla", + "suggest_url": "https://search.yahoo.com/sugg/ff", + "suggest_url_get_params": "output=fxjson&appid=ffd&command={searchTerms}" + } + } +} diff --git a/browser/components/search/extensions/youtube/favicon.ico b/browser/components/search/extensions/youtube/favicon.ico new file mode 100644 index 0000000000000..977887dbbb840 Binary files /dev/null and b/browser/components/search/extensions/youtube/favicon.ico differ diff --git a/browser/components/search/extensions/youtube/manifest.json b/browser/components/search/extensions/youtube/manifest.json new file mode 100644 index 0000000000000..6fbf8745bac2a --- /dev/null +++ b/browser/components/search/extensions/youtube/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "YouTube", + "description": "YouTube - Videos", + "manifest_version": 2, + "version": "1.0", + "applications": { + "gecko": { + "id": "youtube@search.mozilla.org" + } + }, + "hidden": true, + "icons": { + "16": "favicon.ico" + }, + "web_accessible_resources": [ + "favicon.ico" + ], + "chrome_settings_overrides": { + "search_provider": { + "name": "YouTube", + "search_url": "https://www.youtube.com/results?search_query=%7BsearchTerms%7D&search=Se...", + "search_form": "https://www.youtube.com/index", + "suggest_url": "https://suggestqueries.google.com/complete/search?output=firefox&ds=yt&a..." + } + } +} \ No newline at end of file diff --git a/tbb-tests/browser_tor_omnibox.js b/tbb-tests/browser_tor_omnibox.js new file mode 100644 index 0000000000000..3bb90f51a3059 --- /dev/null +++ b/tbb-tests/browser_tor_omnibox.js @@ -0,0 +1,20 @@ +// # Test Tor Omnibox +// Check what search engines are installed in the search box. + +add_task(async function() { + // Grab engine IDs. + let browserSearchService = Components.classes["@mozilla.org/browser/search-service;1"] + .getService(Components.interfaces.nsISearchService), + engineIDs = (await browserSearchService.getEngines()).map(e => e.identifier); + + // Check that we have the correct engines installed, in the right order. + is(engineIDs[0], "ddg", "Default search engine is duckduckgo"); + is(engineIDs[1], "youtube", "Secondary search engine is youtube"); + is(engineIDs[2], "google", "Google is third search engine"); + is(engineIDs[3], "blockchair", "Blockchair is fourth search engine"); + is(engineIDs[4], "ddg-onion", "Duck Duck Go Onion is fifth search engine"); + is(engineIDs[5], "startpage", "Startpage is sixth search engine"); + is(engineIDs[6], "twitter", "Twitter is sixth search engine"); + is(engineIDs[7], "wikipedia", "Wikipedia is seventh search engine"); + is(engineIDs[8], "yahoo", "Yahoo is eighth search engine"); +}); diff --git a/toolkit/components/search/SearchService.jsm b/toolkit/components/search/SearchService.jsm index 58c3eaec2c3e1..80142536e8a95 100644 --- a/toolkit/components/search/SearchService.jsm +++ b/toolkit/components/search/SearchService.jsm @@ -19,7 +19,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { Region: "resource://gre/modules/Region.jsm", RemoteSettings: "resource://services-settings/remote-settings.js", SearchEngine: "resource://gre/modules/SearchEngine.jsm", - SearchEngineSelector: "resource://gre/modules/SearchEngineSelector.jsm", SearchSettings: "resource://gre/modules/SearchSettings.jsm", SearchStaticData: "resource://gre/modules/SearchStaticData.jsm", SearchUtils: "resource://gre/modules/SearchUtils.jsm", @@ -248,11 +247,6 @@ SearchService.prototype = { Services.obs.addObserver(this, Region.REGION_TOPIC);
try { - // Create the search engine selector. - this._engineSelector = new SearchEngineSelector( - this._handleConfigurationUpdated.bind(this) - ); - // See if we have a settings file so we don't have to parse a bunch of XML. let settings = await this._settings.get();
@@ -1055,16 +1049,18 @@ SearchService.prototype = { ? "esr" : AppConstants.MOZ_UPDATE_CHANNEL;
- let { - engines, - privateDefault, - } = await this._engineSelector.fetchEngineConfiguration({ - locale, - region, - channel, - experiment: gExperiment, - distroID: SearchUtils.distroID, - }); + const engines = [ + { webExtension: { id: "ddg@search.mozilla.org" }, orderHint: 100 }, + { webExtension: { id: "youtube@search.mozilla.org" }, orderHint: 90 }, + { webExtension: { id: "google@search.mozilla.org" }, orderHint: 80 }, + { webExtension: { id: "blockchair@search.mozilla.org" }, orderHint: 70 }, + { webExtension: { id: "ddg-onion@search.mozilla.org" }, orderHint: 60 }, + { webExtension: { id: "blockchair-onion@search.mozilla.org" }, orderHint: 50 }, + { webExtension: { id: "startpage@search.mozilla.org" }, orderHint: 40 }, + { webExtension: { id: "twitter@search.mozilla.org" }, orderHint: 30 }, + { webExtension: { id: "wikipedia@search.mozilla.org" }, orderHint: 20 }, + { webExtension: { id: "yahoo@search.mozilla.org" }, orderHint: 10 }, + ];
for (let e of engines) { if (!e.webExtension) { @@ -1073,7 +1069,7 @@ SearchService.prototype = { e.webExtension.locale = e.webExtension?.locale ?? SearchUtils.DEFAULT_TAG; }
- return { engines, privateDefault }; + return { engines, privateDefault: undefined }; },
_setDefaultAndOrdersFromSelector(engines, privateDefault) { diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index fd1e3f75935da..dea49a8e9f79a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -959,6 +959,12 @@ var BuiltInLocation = new (class _BuiltInLocation extends XPIStateLocation { isLinkedAddon(/* aId */) { return false; } + + restore(saved) { + super.restore(saved); + // Bug 33342: avoid restoring disconnect addon from addonStartup.json.lz4. + this.removeAddon("disconnect@search.mozilla.org"); + } })();
/**
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit f406290d35ea9cfa646103074571284fd94eb1c8 Author: Richard Pospesel richard@torproject.org AuthorDate: Fri Jun 8 13:38:40 2018 -0700
Bug 23247: Communicating security expectations for .onion
Encrypting pages hosted on Onion Services with SSL/TLS is redundant (in terms of hiding content) as all traffic within the Tor network is already fully encrypted. Therefore, serving HTTP pages from an Onion Service is more or less fine.
Prior to this patch, Tor Browser would mostly treat pages delivered via Onion Services as well as pages delivered in the ordinary fashion over the internet in the same way. This created some inconsistencies in behaviour and misinformation presented to the user relating to the security of pages delivered via Onion Services:
- HTTP Onion Service pages did not have any 'lock' icon indicating the site was secure - HTTP Onion Service pages would be marked as unencrypted in the Page Info screen - Mixed-mode content restrictions did not apply to HTTP Onion Service pages embedding Non-Onion HTTP content
This patch fixes the above issues, and also adds several new 'Onion' icons to the mix to indicate all of the various permutations of Onion Services hosted HTTP or HTTPS pages with HTTP or HTTPS content.
Strings for Onion Service Page Info page are pulled from Torbutton's localization strings. --- browser/base/content/browser-siteIdentity.js | 39 ++++++++----- browser/base/content/pageinfo/security.js | 64 ++++++++++++++++++---- .../shared/identity-block/identity-block.inc.css | 19 +++++++ dom/base/nsContentUtils.cpp | 19 +++++++ dom/base/nsContentUtils.h | 5 ++ dom/base/nsGlobalWindowOuter.cpp | 3 +- dom/ipc/WindowGlobalActor.cpp | 4 +- dom/ipc/WindowGlobalChild.cpp | 6 +- dom/security/nsMixedContentBlocker.cpp | 16 +++++- .../modules/geckoview/GeckoViewProgress.jsm | 4 ++ security/manager/ssl/nsSecureBrowserUI.cpp | 12 ++++ 11 files changed, 160 insertions(+), 31 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index acbcc79fb21ea..6682ae8b096fe 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -142,6 +142,10 @@ var gIdentityHandler = { ); },
+ get _uriIsOnionHost() { + return this._uriHasHost ? this._uri.host.toLowerCase().endsWith(".onion") : false; + }, + get _isAboutNetErrorPage() { return ( gBrowser.selectedBrowser.documentURI && @@ -745,9 +749,9 @@ var gIdentityHandler = { get pointerlockFsWarningClassName() { // Note that the fullscreen warning does not handle _isSecureInternalUI. if (this._uriHasHost && this._isSecureConnection) { - return "verifiedDomain"; + return this._uriIsOnionHost ? "onionVerifiedDomain" : "verifiedDomain"; } - return "unknownIdentity"; + return this._uriIsOnionHost ? "onionUnknownIdentity" : "unknownIdentity"; },
/** @@ -755,6 +759,10 @@ var gIdentityHandler = { * built-in (returns false) or imported (returns true). */ _hasCustomRoot() { + if (!this._secInfo) { + return false; + } + let issuerCert = null; issuerCert = this._secInfo.succeededCertChain[ this._secInfo.succeededCertChain.length - 1 @@ -797,11 +805,13 @@ var gIdentityHandler = { "identity.extension.label", [extensionName] ); - } else if (this._uriHasHost && this._isSecureConnection) { + } else if (this._uriHasHost && this._isSecureConnection && this._secInfo) { // This is a secure connection. - this._identityBox.className = "verifiedDomain"; + // _isSecureConnection implicitly includes onion services, which may not have an SSL certificate + const uriIsOnionHost = this._uriIsOnionHost; + this._identityBox.className = uriIsOnionHost ? "onionVerifiedDomain" : "verifiedDomain"; if (this._isMixedActiveContentBlocked) { - this._identityBox.classList.add("mixedActiveBlocked"); + this._identityBox.classList.add(uriIsOnionHost ? "onionMixedActiveBlocked" : "mixedActiveBlocked"); } if (!this._isCertUserOverridden) { // It's a normal cert, verifier is the CA Org. @@ -812,17 +822,17 @@ var gIdentityHandler = { } } else if (this._isBrokenConnection) { // This is a secure connection, but something is wrong. - this._identityBox.className = "unknownIdentity"; + const uriIsOnionHost = this._uriIsOnionHost; + this._identityBox.className = uriIsOnionHost ? "onionUnknownIdentity" : "unknownIdentity";
if (this._isMixedActiveContentLoaded) { - this._identityBox.classList.add("mixedActiveContent"); + this._identityBox.classList.add(uriIsOnionHost ? "onionMixedActiveContent" : "mixedActiveContent"); } else if (this._isMixedActiveContentBlocked) { - this._identityBox.classList.add( - "mixedDisplayContentLoadedActiveBlocked" - ); + this._identityBox.classList.add(uriIsOnionHost ? "onionMixedDisplayContentLoadedActiveBlocked" : "mixedDisplayContentLoadedActiveBlocked"); } else if (this._isMixedPassiveContentLoaded) { - this._identityBox.classList.add("mixedDisplayContent"); + this._identityBox.classList.add(uriIsOnionHost ? "onionMixedDisplayContent" : "mixedDisplayContent"); } else { + // TODO: ignore weak https cipher for onionsites? this._identityBox.classList.add("weakCipher"); } } else if (this._isAboutCertErrorPage) { @@ -835,8 +845,8 @@ var gIdentityHandler = { // Network errors and blocked pages get a more neutral icon this._identityBox.className = "unknownIdentity"; } else if (this._isPotentiallyTrustworthy) { - // This is a local resource (and shouldn't be marked insecure). - this._identityBox.className = "localResource"; + // This is a local resource or an onion site (and shouldn't be marked insecure). + this._identityBox.className = this._uriIsOnionHost ? "onionUnknownIdentity" : "localResource"; } else { // This is an insecure connection. let warnOnInsecure = @@ -860,7 +870,8 @@ var gIdentityHandler = { }
if (this._isCertUserOverridden) { - this._identityBox.classList.add("certUserOverridden"); + const uriIsOnionHost = this._uriIsOnionHost; + this._identityBox.classList.add(uriIsOnionHost ? "onionCertUserOverridden" : "certUserOverridden"); // Cert is trusted because of a security exception, verifier is a special string. tooltip = gNavigatorBundle.getString( "identity.identified.verified_by_you" diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js index 1222c8b0ec35d..8d10c8df814c4 100644 --- a/browser/base/content/pageinfo/security.js +++ b/browser/base/content/pageinfo/security.js @@ -22,6 +22,13 @@ ChromeUtils.defineModuleGetter( "PluralForm", "resource://gre/modules/PluralForm.jsm" ); +XPCOMUtils.defineLazyGetter( + this, + "gTorButtonBundle", + function() { + return Services.strings.createBundle("chrome://torbutton/locale/torbutton.properties"); + } +);
var security = { async init(uri, windowInfo) { @@ -60,6 +67,11 @@ var security = { (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT | Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); var isEV = ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL; + var isOnion = false; + const hostName = this.windowInfo.hostName; + if (hostName && hostName.endsWith(".onion")) { + isOnion = true; + }
let retval = { cAName: "", @@ -69,6 +81,7 @@ var security = { isBroken, isMixed, isEV, + isOnion, cert: null, certificateTransparency: null, }; @@ -107,6 +120,7 @@ var security = { isBroken, isMixed, isEV, + isOnion, cert, certChain: certChainArray, certificateTransparency: undefined, @@ -348,22 +362,50 @@ async function securityOnLoad(uri, windowInfo) { } msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); } else if (info.encryptionStrength > 0) { - hdr = pkiBundle.getFormattedString( - "pageInfo_EncryptionWithBitsAndProtocol", - [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] - ); + if (!info.isOnion) { + hdr = pkiBundle.getFormattedString( + "pageInfo_EncryptionWithBitsAndProtocol", + [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] + ); + } else { + try { + hdr = gTorButtonBundle.formatStringFromName( + "pageInfo_OnionEncryptionWithBitsAndProtocol", + [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] + ); + } catch(err) { + hdr = "Connection Encrypted (Onion Service, " + + info.encryptionAlgorithm + + ", " + + info.encryptionStrength + + " bit keys, " + + info.version + + ")"; + } + } msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1"); msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2"); } else { - hdr = pkiBundle.getString("pageInfo_NoEncryption"); - if (windowInfo.hostName != null) { - msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [ - windowInfo.hostName, - ]); + if (!info.isOnion) { + hdr = pkiBundle.getString("pageInfo_NoEncryption"); + if (windowInfo.hostName != null) { + msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [ + windowInfo.hostName, + ]); + } else { + msg1 = pkiBundle.getString("pageInfo_Privacy_None4"); + } + msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); } else { - msg1 = pkiBundle.getString("pageInfo_Privacy_None4"); + try { + hdr = gTorButtonBundle.GetStringFromName("pageInfo_OnionEncryption"); + } catch (err) { + hdr = "Connection Encrypted (Onion Service)"; + } + + msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1"); + msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2"); } - msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); } setText("security-technical-shortform", hdr); setText("security-technical-longform1", msg1); diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css index a01e7d705cd72..68ed0beed6842 100644 --- a/browser/themes/shared/identity-block/identity-block.inc.css +++ b/browser/themes/shared/identity-block/identity-block.inc.css @@ -203,6 +203,25 @@ list-style-image: url(chrome://global/skin/icons/security-broken.svg); }
+#identity-box[pageproxystate="valid"].onionUnknownIdentity #identity-icon, +#identity-box[pageproxystate="valid"].onionVerifiedDomain #identity-icon, +#identity-box[pageproxystate="valid"].onionMixedActiveBlocked #identity-icon { + list-style-image: url(chrome://browser/skin/onion.svg); + visibility: visible; +} + +#identity-box[pageproxystate="valid"].onionMixedDisplayContent #identity-icon, +#identity-box[pageproxystate="valid"].onionMixedDisplayContentLoadedActiveBlocked #identity-icon, +#identity-box[pageproxystate="valid"].onionCertUserOverridden #identity-icon { + list-style-image: url(chrome://browser/skin/onion-warning.svg); + visibility: visible; +} + +#identity-box[pageproxystate="valid"].onionMixedActiveContent #identity-icon { + list-style-image: url(chrome://browser/skin/onion-slash.svg); + visibility: visible; +} + #permissions-granted-icon { list-style-image: url(chrome://browser/skin/permissions.svg); } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index fac49bab17f23..6dd69d1d81a8a 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -9325,6 +9325,25 @@ bool nsContentUtils::ComputeIsSecureContext(nsIChannel* aChannel) { return principal->GetIsOriginPotentiallyTrustworthy(); }
+/* static */ bool nsContentUtils::DocumentHasOnionURI(Document* aDocument) { + if (!aDocument) { + return false; + } + + nsIURI* uri = aDocument->GetDocumentURI(); + if (!uri) { + return false; + } + + nsAutoCString host; + if (NS_SUCCEEDED(uri->GetHost(host))) { + bool hasOnionURI = StringEndsWith(host, ".onion"_ns); + return hasOnionURI; + } + + return false; +} + /* static */ void nsContentUtils::TryToUpgradeElement(Element* aElement) { NodeInfo* nodeInfo = aElement->NodeInfo(); diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index baa75be5b5702..bb5c84886af9a 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -2999,6 +2999,11 @@ class nsContentUtils { */ static bool HttpsStateIsModern(Document* aDocument);
+ /** + * Returns true of the document's URI is a .onion + */ + static bool DocumentHasOnionURI(Document* aDocument); + /** * Returns true if the channel is for top-level window and is over secure * context. diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index aab4a37e78a8f..e981573e9822b 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -1880,7 +1880,8 @@ bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument, return false; }
- if (nsContentUtils::HttpsStateIsModern(aDocument)) { + if (nsContentUtils::HttpsStateIsModern(aDocument) || + nsContentUtils::DocumentHasOnionURI(aDocument)) { return true; }
diff --git a/dom/ipc/WindowGlobalActor.cpp b/dom/ipc/WindowGlobalActor.cpp index 8a3b49edd4d70..9975136e8e18a 100644 --- a/dom/ipc/WindowGlobalActor.cpp +++ b/dom/ipc/WindowGlobalActor.cpp @@ -21,6 +21,7 @@ #include "mozilla/net/CookieJarSettings.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/nsMixedContentBlocker.h"
#include "nsGlobalWindowInner.h" #include "nsNetUtil.h" @@ -131,7 +132,8 @@ WindowGlobalInit WindowGlobalActor::WindowInitializer(
// Init Mixed Content Fields nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(doc->GetDocumentURI()); - fields.mIsSecure = innerDocURI && innerDocURI->SchemeIs("https"); + fields.mIsSecure = innerDocURI && (innerDocURI->SchemeIs("https") || + nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI));
nsCOMPtr<nsITransportSecurityInfo> securityInfo; if (nsCOMPtr<nsIChannel> channel = doc->GetChannel()) { diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp index 84c060c415341..73ac6a0cf96d2 100644 --- a/dom/ipc/WindowGlobalChild.cpp +++ b/dom/ipc/WindowGlobalChild.cpp @@ -48,6 +48,8 @@ # include "GeckoProfiler.h" #endif
+#include "mozilla/dom/nsMixedContentBlocker.h" + using namespace mozilla::ipc; using namespace mozilla::dom::ipc;
@@ -234,7 +236,9 @@ void WindowGlobalChild::OnNewDocument(Document* aDocument) { nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(aDocument->GetDocumentURI()); if (innerDocURI) { - txn.SetIsSecure(innerDocURI->SchemeIs("https")); + txn.SetIsSecure( + innerDocURI->SchemeIs("https") || + nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI)); }
MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index 01c7877e020d3..dab3f19bad409 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -634,8 +634,8 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, return NS_OK; }
- // Check the parent scheme. If it is not an HTTPS page then mixed content - // restrictions do not apply. + // Check the parent scheme. If it is not an HTTPS or .onion page then mixed + // content restrictions do not apply. nsCOMPtr<nsIURI> innerRequestingLocation = NS_GetInnermostURI(requestingLocation); if (!innerRequestingLocation) { @@ -650,6 +650,17 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
bool parentIsHttps = innerRequestingLocation->SchemeIs("https"); if (!parentIsHttps) { + bool parentIsOnion = IsPotentiallyTrustworthyOnion(innerRequestingLocation); + if (!parentIsOnion) { + *aDecision = ACCEPT; + return NS_OK; + } + } + + bool isHttpScheme = innerContentLocation->SchemeIs("http"); + // .onion URLs are encrypted and authenticated. Don't treat them as mixed + // content if potentially trustworthy (i.e. whitelisted). + if (isHttpScheme && IsPotentiallyTrustworthyOnion(innerContentLocation)) { *aDecision = ACCEPT; MOZ_LOG(sMCBLog, LogLevel::Verbose, (" -> decision: Request will be allowed because the requesting " @@ -676,7 +687,6 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, return NS_OK; }
- bool isHttpScheme = innerContentLocation->SchemeIs("http"); if (isHttpScheme && IsPotentiallyTrustworthyOrigin(innerContentLocation)) { *aDecision = ACCEPT; return NS_OK; diff --git a/mobile/android/modules/geckoview/GeckoViewProgress.jsm b/mobile/android/modules/geckoview/GeckoViewProgress.jsm index 17069dbe657f7..c1346b1858cf1 100644 --- a/mobile/android/modules/geckoview/GeckoViewProgress.jsm +++ b/mobile/android/modules/geckoview/GeckoViewProgress.jsm @@ -145,6 +145,10 @@ var IdentityHandler = { result.host = uri.host; }
+ if (!aBrowser.securityUI.secInfo) { + return result; + } + const cert = aBrowser.securityUI.secInfo.serverCert;
result.certificate = aBrowser.securityUI.secInfo.serverCert.getBase64DERString(); diff --git a/security/manager/ssl/nsSecureBrowserUI.cpp b/security/manager/ssl/nsSecureBrowserUI.cpp index b4de1a331ffcf..f1ce395828547 100644 --- a/security/manager/ssl/nsSecureBrowserUI.cpp +++ b/security/manager/ssl/nsSecureBrowserUI.cpp @@ -9,6 +9,7 @@ #include "mozilla/Logging.h" #include "mozilla/Unused.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/nsMixedContentBlocker.h" #include "nsContentUtils.h" #include "nsIChannel.h" #include "nsDocShell.h" @@ -85,6 +86,17 @@ void nsSecureBrowserUI::RecomputeSecurityFlags() { } } } + + // any protocol routed over tor is secure + if (!(mState & nsIWebProgressListener::STATE_IS_SECURE)) { + nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(win->GetDocumentURI()); + if (innerDocURI && + nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI)) { + MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" is onion")); + mState = (mState & ~nsIWebProgressListener::STATE_IS_INSECURE) | + nsIWebProgressListener::STATE_IS_SECURE; + } + } }
// Add upgraded-state flags when request has been
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit c76ed4df6694fe287bc6b3998cd19733f1ac13ad Author: Kathy Brade brade@pearlcrescent.com AuthorDate: Tue Nov 12 16:11:05 2019 -0500
Bug 30237: Add v3 onion services client authentication prompt
When Tor informs the browser that client authentication is needed, temporarily load about:blank instead of about:neterror and prompt for the user's key.
If a correctly formatted key is entered, use Tor's ONION_CLIENT_AUTH_ADD control port command to add the key (via Torbutton's control port module) and reload the page.
If the user cancels the prompt, display the standard about:neterror "Unable to connect" page. This requires a small change to browser/actors/NetErrorChild.jsm to account for the fact that the docShell no longer has the failedChannel information. The failedChannel is used to extract TLS-related error info, which is not applicable in the case of a canceled .onion authentication prompt.
Add a leaveOpen option to PopupNotifications.show so we can display error messages within the popup notification doorhanger without closing the prompt.
Add support for onion services strings to the TorStrings module.
Add support for Tor extended SOCKS errors (Tor proposal 304) to the socket transport and SOCKS layers. Improved display of all of these errors will be implemented as part of bug 30025.
Also fixes bug 19757: Add a "Remember this key" checkbox to the client auth prompt.
Add an "Onion Services Authentication" section within the about:preferences "Privacy & Security section" to allow viewing and removal of v3 onion client auth keys that have been stored on disk.
Also fixes bug 19251: use enhanced error pages for onion service errors. --- browser/actors/NetErrorChild.jsm | 7 + browser/base/content/browser.js | 10 + browser/base/content/browser.xhtml | 1 + browser/base/content/certerror/aboutNetError.js | 10 +- browser/base/content/certerror/aboutNetError.xhtml | 1 + browser/base/content/main-popupset.inc.xhtml | 1 + browser/base/content/navigator-toolbox.inc.xhtml | 1 + browser/base/content/tab-content.js | 6 + browser/components/moz.build | 1 + .../content/authNotificationIcon.inc.xhtml | 6 + .../onionservices/content/authPopup.inc.xhtml | 16 ++ .../onionservices/content/authPreferences.css | 20 ++ .../content/authPreferences.inc.xhtml | 19 ++ .../onionservices/content/authPreferences.js | 66 +++++ .../components/onionservices/content/authPrompt.js | 320 +++++++++++++++++++++ .../components/onionservices/content/authUtil.jsm | 47 +++ .../onionservices/content/netError/browser.svg | 3 + .../onionservices/content/netError/network.svg | 3 + .../content/netError/onionNetError.css | 88 ++++++ .../content/netError/onionNetError.js | 243 ++++++++++++++++ .../onionservices/content/netError/onionsite.svg | 8 + .../onionservices/content/onionservices.css | 69 +++++ .../onionservices/content/savedKeysDialog.js | 259 +++++++++++++++++ .../onionservices/content/savedKeysDialog.xhtml | 42 +++ browser/components/onionservices/jar.mn | 9 + browser/components/onionservices/moz.build | 1 + browser/components/preferences/preferences.xhtml | 1 + browser/components/preferences/privacy.inc.xhtml | 2 + browser/components/preferences/privacy.js | 7 + browser/themes/shared/notification-icons.inc.css | 3 + docshell/base/nsDocShell.cpp | 81 +++++- dom/ipc/BrowserParent.cpp | 21 ++ dom/ipc/BrowserParent.h | 3 + dom/ipc/PBrowser.ipdl | 9 + js/xpconnect/src/xpc.msg | 10 + netwerk/base/nsSocketTransport2.cpp | 6 + netwerk/socket/nsSOCKSIOLayer.cpp | 49 ++++ toolkit/modules/PopupNotifications.jsm | 6 + toolkit/modules/RemotePageAccessManager.jsm | 1 + .../lib/environments/frame-script.js | 1 + xpcom/base/ErrorList.py | 22 ++ 41 files changed, 1477 insertions(+), 2 deletions(-)
diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm index 82978412fe24e..164fb7c95cd1c 100644 --- a/browser/actors/NetErrorChild.jsm +++ b/browser/actors/NetErrorChild.jsm @@ -13,6 +13,8 @@ const { RemotePageChild } = ChromeUtils.import( "resource://gre/actors/RemotePageChild.jsm" );
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + XPCOMUtils.defineLazyServiceGetter( this, "gSerializationHelper", @@ -33,6 +35,7 @@ class NetErrorChild extends RemotePageChild { "RPMAddToHistogram", "RPMRecordTelemetryEvent", "RPMGetHttpResponseHeader", + "RPMGetTorStrings", ]; this.exportFunctions(exportableFunctions); } @@ -115,4 +118,8 @@ class NetErrorChild extends RemotePageChild {
return ""; } + + RPMGetTorStrings() { + return Cu.cloneInto(TorStrings.onionServices, this.contentWindow); + } } diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index fd33c49a86802..f6621a9a74014 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -229,6 +229,11 @@ XPCOMUtils.defineLazyScriptGetter( ["SecurityLevelButton"], "chrome://browser/content/securitylevel/securityLevel.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionAuthPrompt"], + "chrome://browser/content/onionservices/authPrompt.js" +); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", @@ -1772,6 +1777,9 @@ var gBrowserInit = { // Init the SecuritySettingsButton SecurityLevelButton.init();
+ // Init the OnionAuthPrompt + OnionAuthPrompt.init(); + // Certain kinds of automigration rely on this notification to complete // their tasks BEFORE the browser window is shown. SessionStore uses it to // restore tabs into windows AFTER important parts like gMultiProcessBrowser @@ -2507,6 +2515,8 @@ var gBrowserInit = {
SecurityLevelButton.uninit();
+ OnionAuthPrompt.uninit(); + TorBootstrapUrlbar.uninit();
gAccessibilityServiceIndicator.uninit(); diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 627e6ac0f8a09..394a464140186 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -34,6 +34,7 @@ <?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css" type="text/css"?> <?xml-stylesheet href="chrome://torbutton/skin/tor-circuit-display.css" type="text/css"?> <?xml-stylesheet href="chrome://torbutton/skin/torbutton.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/onionservices.css" type="text/css"?>
# All DTD information is stored in a separate file so that it can be shared by # hiddenWindowMac.xhtml. diff --git a/browser/base/content/certerror/aboutNetError.js b/browser/base/content/certerror/aboutNetError.js index edf97c2a5daf3..60f602ba65303 100644 --- a/browser/base/content/certerror/aboutNetError.js +++ b/browser/base/content/certerror/aboutNetError.js @@ -3,6 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env mozilla/frame-script */ +/* import-globals-from ../../components/onionservices/content/netError/onionNetError.js */
import "chrome://global/content/certviewer/pvutils_bundle.js"; import "chrome://global/content/certviewer/asn1js_bundle.js"; @@ -317,7 +318,10 @@ async function initPage() { errDesc = document.getElementById("ed_generic"); }
- setErrorPageStrings(err); + const isOnionError = err.startsWith("onionServices."); + if (!isOnionError) { + setErrorPageStrings(err); + }
var sd = document.getElementById("errorShortDescText"); if (sd) { @@ -469,6 +473,10 @@ async function initPage() { span.textContent = HOST_NAME; } } + + if (isOnionError) { + OnionServicesAboutNetError.initPage(document); + } }
function setupBlockingReportingUI() { diff --git a/browser/base/content/certerror/aboutNetError.xhtml b/browser/base/content/certerror/aboutNetError.xhtml index c645a2f2cc77a..bf9a8fd58347a 100644 --- a/browser/base/content/certerror/aboutNetError.xhtml +++ b/browser/base/content/certerror/aboutNetError.xhtml @@ -209,5 +209,6 @@ </div> </body> <script src="chrome://browser/content/certerror/aboutNetErrorCodes.js"/> + <script src="chrome://browser/content/onionservices/netError/onionNetError.js"/> <script type="module" src="chrome://browser/content/certerror/aboutNetError.js"/> </html> diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml index 3fc665c65d793..a96aaa9c187df 100644 --- a/browser/base/content/main-popupset.inc.xhtml +++ b/browser/base/content/main-popupset.inc.xhtml @@ -521,6 +521,7 @@ #include ../../components/downloads/content/downloadsPanel.inc.xhtml #include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml #include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml +#include ../../components/onionservices/content/authPopup.inc.xhtml #include browser-allTabsMenu.inc.xhtml
<tooltip id="dynamic-shortcut-tooltip" diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index e10e0580b8ecb..810a77e57766d 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -268,6 +268,7 @@ data-l10n-id="urlbar-indexed-db-notification-anchor"/> <image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button" data-l10n-id="urlbar-password-notification-anchor"/> +#include ../../components/onionservices/content/authNotificationIcon.inc.xhtml <stack id="plugins-notification-icon" class="notification-anchor-icon" role="button" align="center" data-l10n-id="urlbar-plugins-notification-anchor"> <image class="plugin-icon" /> <image id="plugin-icon-badge" /> diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js index 83e55cf5ed878..96360a4307d24 100644 --- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -7,4 +7,10 @@
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { OnionAuthUtil } = ChromeUtils.import( + "chrome://browser/content/onionservices/authUtil.jsm" +); + Services.obs.notifyObservers(this, "tab-content-frameloader-created"); + +OnionAuthUtil.addCancelMessageListener(this, docShell); diff --git a/browser/components/moz.build b/browser/components/moz.build index d29df1d3df99a..c304973749122 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -38,6 +38,7 @@ DIRS += [ "extensions", "migration", "newtab", + "onionservices", "originattributes", "ion", "places", diff --git a/browser/components/onionservices/content/authNotificationIcon.inc.xhtml b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml new file mode 100644 index 0000000000000..91274d6127392 --- /dev/null +++ b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml @@ -0,0 +1,6 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<image id="tor-clientauth-notification-icon" + class="notification-anchor-icon tor-clientauth-icon" + role="button" + tooltiptext="&torbutton.onionServices.authPrompt.tooltip;"/> diff --git a/browser/components/onionservices/content/authPopup.inc.xhtml b/browser/components/onionservices/content/authPopup.inc.xhtml new file mode 100644 index 0000000000000..bd0ec3aa0b007 --- /dev/null +++ b/browser/components/onionservices/content/authPopup.inc.xhtml @@ -0,0 +1,16 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<popupnotification id="tor-clientauth-notification" hidden="true"> + <popupnotificationcontent orient="vertical"> + <description id="tor-clientauth-notification-desc"/> + <label id="tor-clientauth-notification-learnmore" + class="text-link popup-notification-learnmore-link" + is="text-link"/> + html:div + <html:input id="tor-clientauth-notification-key" type="password"/> + <html:div id="tor-clientauth-warning"/> + <checkbox id="tor-clientauth-persistkey-checkbox" + label="&torbutton.onionServices.authPrompt.persistCheckboxLabel;"/> + </html:div> + </popupnotificationcontent> +</popupnotification> diff --git a/browser/components/onionservices/content/authPreferences.css b/browser/components/onionservices/content/authPreferences.css new file mode 100644 index 0000000000000..b3fb79b26ddce --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.css @@ -0,0 +1,20 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +#torOnionServiceKeys-overview-container { + margin-right: 30px; +} + +#onionservices-savedkeys-tree treechildren::-moz-tree-cell-text { + font-size: 80%; +} + +#onionservices-savedkeys-errorContainer { + margin-top: 4px; + min-height: 3em; +} + +#onionservices-savedkeys-errorIcon { + margin-right: 4px; + list-style-image: url("chrome://browser/skin/warning.svg"); + visibility: hidden; +} diff --git a/browser/components/onionservices/content/authPreferences.inc.xhtml b/browser/components/onionservices/content/authPreferences.inc.xhtml new file mode 100644 index 0000000000000..f69c9dde66a2e --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.inc.xhtml @@ -0,0 +1,19 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<groupbox id="torOnionServiceKeys" orient="vertical" + data-category="panePrivacy" hidden="true"> + <label><html:h2 id="torOnionServiceKeys-header"/></label> + <hbox> + <description id="torOnionServiceKeys-overview-container" flex="1"> + <html:span id="torOnionServiceKeys-overview" + class="tail-with-learn-more"/> + <label id="torOnionServiceKeys-learnMore" class="learnMore text-link" + is="text-link"/> + </description> + <vbox align="end"> + <button id="torOnionServiceKeys-savedKeys" + is="highlightable-button" + class="accessory-button"/> + </vbox> + </hbox> +</groupbox> diff --git a/browser/components/onionservices/content/authPreferences.js b/browser/components/onionservices/content/authPreferences.js new file mode 100644 index 0000000000000..52f8272020cca --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.js @@ -0,0 +1,66 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +/* + Onion Services Client Authentication Preferences Code + + Code to handle init and update of onion services authentication section + in about:preferences#privacy +*/ + +const OnionServicesAuthPreferences = { + selector: { + groupBox: "#torOnionServiceKeys", + header: "#torOnionServiceKeys-header", + overview: "#torOnionServiceKeys-overview", + learnMore: "#torOnionServiceKeys-learnMore", + savedKeysButton: "#torOnionServiceKeys-savedKeys", + }, + + init() { + // populate XUL with localized strings + this._populateXUL(); + }, + + _populateXUL() { + const groupbox = document.querySelector(this.selector.groupBox); + + let elem = groupbox.querySelector(this.selector.header); + elem.textContent = TorStrings.onionServices.authPreferences.header; + + elem = groupbox.querySelector(this.selector.overview); + elem.textContent = TorStrings.onionServices.authPreferences.overview; + + elem = groupbox.querySelector(this.selector.learnMore); + elem.setAttribute("value", TorStrings.onionServices.learnMore); + elem.setAttribute("href", TorStrings.onionServices.learnMoreURL); + + elem = groupbox.querySelector(this.selector.savedKeysButton); + elem.setAttribute( + "label", + TorStrings.onionServices.authPreferences.savedKeys + ); + elem.addEventListener("command", () => + OnionServicesAuthPreferences.onViewSavedKeys() + ); + }, + + onViewSavedKeys() { + gSubDialog.open( + "chrome://browser/content/onionservices/savedKeysDialog.xhtml" + ); + }, +}; // OnionServicesAuthPreferences + +Object.defineProperty(this, "OnionServicesAuthPreferences", { + value: OnionServicesAuthPreferences, + enumerable: true, + writable: false, +}); diff --git a/browser/components/onionservices/content/authPrompt.js b/browser/components/onionservices/content/authPrompt.js new file mode 100644 index 0000000000000..df609df44e491 --- /dev/null +++ b/browser/components/onionservices/content/authPrompt.js @@ -0,0 +1,320 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +XPCOMUtils.defineLazyModuleGetters(this, { + OnionAuthUtil: "chrome://browser/content/onionservices/authUtil.jsm", + CommonUtils: "resource://services-common/utils.js", + TorStrings: "resource:///modules/TorStrings.jsm", +}); + +const OnionAuthPrompt = (function() { + // OnionServicesAuthPrompt objects run within the main/chrome process. + // aReason is the topic passed within the observer notification that is + // causing this auth prompt to be displayed. + function OnionServicesAuthPrompt(aBrowser, aFailedURI, aReason, aOnionName) { + this._browser = aBrowser; + this._failedURI = aFailedURI; + this._reasonForPrompt = aReason; + this._onionName = aOnionName; + } + + OnionServicesAuthPrompt.prototype = { + show(aWarningMessage) { + let mainAction = { + label: TorStrings.onionServices.authPrompt.done, + accessKey: TorStrings.onionServices.authPrompt.doneAccessKey, + leaveOpen: true, // Callback is responsible for closing the notification. + callback: this._onDone.bind(this), + }; + + let dialogBundle = Services.strings.createBundle( + "chrome://global/locale/dialog.properties"); + + let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel"); + if (!cancelAccessKey) + cancelAccessKey = "c"; // required by PopupNotifications.show() + + let cancelAction = { + label: dialogBundle.GetStringFromName("button-cancel"), + accessKey: cancelAccessKey, + callback: this._onCancel.bind(this), + }; + + let _this = this; + let options = { + autofocus: true, + hideClose: true, + persistent: true, + removeOnDismissal: false, + eventCallback(aTopic) { + if (aTopic === "showing") { + _this._onPromptShowing(aWarningMessage); + } else if (aTopic === "shown") { + _this._onPromptShown(); + } else if (aTopic === "removed") { + _this._onPromptRemoved(); + } + } + }; + + this._prompt = PopupNotifications.show(this._browser, + OnionAuthUtil.domid.notification, "", + OnionAuthUtil.domid.anchor, + mainAction, [cancelAction], options); + }, + + _onPromptShowing(aWarningMessage) { + let xulDoc = this._browser.ownerDocument; + let descElem = xulDoc.getElementById(OnionAuthUtil.domid.description); + if (descElem) { + // Handle replacement of the onion name within the localized + // string ourselves so we can show the onion name as bold text. + // We do this by splitting the localized string and creating + // several HTML <span> elements. + while (descElem.firstChild) + descElem.removeChild(descElem.firstChild); + + let fmtString = TorStrings.onionServices.authPrompt.description; + let prefix = ""; + let suffix = ""; + const kToReplace = "%S"; + let idx = fmtString.indexOf(kToReplace); + if (idx < 0) { + prefix = fmtString; + } else { + prefix = fmtString.substring(0, idx); + suffix = fmtString.substring(idx + kToReplace.length); + } + + const kHTMLNS = "http://www.w3.org/1999/xhtml"; + let span = xulDoc.createElementNS(kHTMLNS, "span"); + span.textContent = prefix; + descElem.appendChild(span); + span = xulDoc.createElementNS(kHTMLNS, "span"); + span.id = OnionAuthUtil.domid.onionNameSpan; + span.textContent = this._onionName; + descElem.appendChild(span); + span = xulDoc.createElementNS(kHTMLNS, "span"); + span.textContent = suffix; + descElem.appendChild(span); + } + + // Set "Learn More" label and href. + let learnMoreElem = xulDoc.getElementById(OnionAuthUtil.domid.learnMore); + if (learnMoreElem) { + learnMoreElem.setAttribute("value", TorStrings.onionServices.learnMore); + learnMoreElem.setAttribute("href", TorStrings.onionServices.learnMoreURL); + } + + this._showWarning(aWarningMessage); + let checkboxElem = this._getCheckboxElement(); + if (checkboxElem) { + checkboxElem.checked = false; + } + }, + + _onPromptShown() { + let keyElem = this._getKeyElement(); + if (keyElem) { + keyElem.setAttribute("placeholder", + TorStrings.onionServices.authPrompt.keyPlaceholder); + this._boundOnKeyFieldKeyPress = this._onKeyFieldKeyPress.bind(this); + this._boundOnKeyFieldInput = this._onKeyFieldInput.bind(this); + keyElem.addEventListener("keypress", this._boundOnKeyFieldKeyPress); + keyElem.addEventListener("input", this._boundOnKeyFieldInput); + keyElem.focus(); + } + }, + + _onPromptRemoved() { + if (this._boundOnKeyFieldKeyPress) { + let keyElem = this._getKeyElement(); + if (keyElem) { + keyElem.value = ""; + keyElem.removeEventListener("keypress", + this._boundOnKeyFieldKeyPress); + this._boundOnKeyFieldKeyPress = undefined; + keyElem.removeEventListener("input", this._boundOnKeyFieldInput); + this._boundOnKeyFieldInput = undefined; + } + } + }, + + _onKeyFieldKeyPress(aEvent) { + if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { + this._onDone(); + } else if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) { + this._prompt.remove(); + this._onCancel(); + } + }, + + _onKeyFieldInput(aEvent) { + this._showWarning(undefined); // Remove the warning. + }, + + async _onDone() { + let keyElem = this._getKeyElement(); + if (!keyElem) + return; + + let base64key = this._keyToBase64(keyElem.value); + if (!base64key) { + this._showWarning(TorStrings.onionServices.authPrompt.invalidKey); + return; + } + + this._prompt.remove(); + + // Use Torbutton's controller module to add the private key to Tor. + let controllerFailureMsg = + TorStrings.onionServices.authPrompt.failedToSetKey; + try { + let { controller } = + Cu.import("resource://torbutton/modules/tor-control-port.js", {}); + let torController = await controller(aError => { + console.error(controllerFailureMsg, aError); + this.show(controllerFailureMsg); + }); + let onionAddr = this._onionName.toLowerCase().replace(/.onion$/, ""); + let checkboxElem = this._getCheckboxElement(); + let isPermanent = (checkboxElem && checkboxElem.checked); + torController.onionAuthAdd(onionAddr, base64key, isPermanent) + .then(aResponse => { + // Success! Reload the page. + this._browser.sendMessageToActor( + "Browser:Reload", + {}, + "BrowserTab" + ); + }) + .catch(aError => { + if (aError.torMessage) { + this.show(aError.torMessage); + } else { + console.error(controllerFailureMsg, aError); + this.show(controllerFailureMsg); + } + }); + } catch (e) { + console.error(controllerFailureMsg, e); + this.show(controllerFailureMsg); + } + }, + + _onCancel() { + // Arrange for an error page to be displayed. + this._browser.messageManager.sendAsyncMessage( + OnionAuthUtil.message.authPromptCanceled, + {failedURI: this._failedURI.spec, + reasonForPrompt: this._reasonForPrompt}); + }, + + _getKeyElement() { + let xulDoc = this._browser.ownerDocument; + return xulDoc.getElementById(OnionAuthUtil.domid.keyElement); + }, + + _getCheckboxElement() { + let xulDoc = this._browser.ownerDocument; + return xulDoc.getElementById(OnionAuthUtil.domid.checkboxElement); + }, + + _showWarning(aWarningMessage) { + let xulDoc = this._browser.ownerDocument; + let warningElem = + xulDoc.getElementById(OnionAuthUtil.domid.warningElement); + let keyElem = this._getKeyElement(); + if (warningElem) { + if (aWarningMessage) { + warningElem.textContent = aWarningMessage; + warningElem.removeAttribute("hidden"); + if (keyElem) + keyElem.className = "invalid"; + } else { + warningElem.setAttribute("hidden", "true"); + if (keyElem) + keyElem.className = ""; + } + } + }, + + // Returns undefined if the key is the wrong length or format. + _keyToBase64(aKeyString) { + if (!aKeyString) + return undefined; + + let base64key; + if (aKeyString.length == 52) { + // The key is probably base32-encoded. Attempt to decode. + // Although base32 specifies uppercase letters, we accept lowercase + // as well because users may type in lowercase or copy a key out of + // a tor onion-auth file (which uses lowercase). + let rawKey; + try { + rawKey = CommonUtils.decodeBase32(aKeyString.toUpperCase()); + } catch (e) {} + + if (rawKey) try { + base64key = btoa(rawKey); + } catch (e) {} + } else if ((aKeyString.length == 44) && + /^[a-zA-Z0-9+/]*=*$/.test(aKeyString)) { + // The key appears to be a correctly formatted base64 value. If not, + // tor will return an error when we try to add the key via the + // control port. + base64key = aKeyString; + } + + return base64key; + }, + }; + + let retval = { + init() { + Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthMissing); + Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthIncorrect); + }, + + uninit() { + Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthMissing); + Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthIncorrect); + }, + + // aSubject is the DOM Window or browser where the prompt should be shown. + // aData contains the .onion name. + observe(aSubject, aTopic, aData) { + if ((aTopic != OnionAuthUtil.topic.clientAuthMissing) && + (aTopic != OnionAuthUtil.topic.clientAuthIncorrect)) { + return; + } + + let browser; + if (aSubject instanceof Ci.nsIDOMWindow) { + let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow); + browser = contentWindow.docShell.chromeEventHandler; + } else { + browser = aSubject.QueryInterface(Ci.nsIBrowser); + } + + if (!gBrowser.browsers.some(aBrowser => aBrowser == browser)) { + return; // This window does not contain the subject browser; ignore. + } + + let failedURI = browser.currentURI; + let authPrompt = new OnionServicesAuthPrompt(browser, failedURI, + aTopic, aData); + authPrompt.show(undefined); + } + }; + + return retval; +})(); /* OnionAuthPrompt */ + + +Object.defineProperty(this, "OnionAuthPrompt", { + value: OnionAuthPrompt, + enumerable: true, + writable: false +}); diff --git a/browser/components/onionservices/content/authUtil.jsm b/browser/components/onionservices/content/authUtil.jsm new file mode 100644 index 0000000000000..c9d83774da1fd --- /dev/null +++ b/browser/components/onionservices/content/authUtil.jsm @@ -0,0 +1,47 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = [ + "OnionAuthUtil", +]; + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const OnionAuthUtil = { + topic: { + clientAuthMissing: "tor-onion-services-clientauth-missing", + clientAuthIncorrect: "tor-onion-services-clientauth-incorrect", + }, + message: { + authPromptCanceled: "Tor:OnionServicesAuthPromptCanceled", + }, + domid: { + anchor: "tor-clientauth-notification-icon", + notification: "tor-clientauth", + description: "tor-clientauth-notification-desc", + learnMore: "tor-clientauth-notification-learnmore", + onionNameSpan: "tor-clientauth-notification-onionname", + keyElement: "tor-clientauth-notification-key", + warningElement: "tor-clientauth-warning", + checkboxElement: "tor-clientauth-persistkey-checkbox", + }, + + addCancelMessageListener(aTabContent, aDocShell) { + aTabContent.addMessageListener(this.message.authPromptCanceled, + (aMessage) => { + // Upon cancellation of the client authentication prompt, display + // the appropriate error page. When calling the docShell + // displayLoadError() function, we pass undefined for the failed + // channel so that displayLoadError() can determine that it should + // not display the client authentication prompt a second time. + let failedURI = Services.io.newURI(aMessage.data.failedURI); + let reasonForPrompt = aMessage.data.reasonForPrompt; + let errorCode = + (reasonForPrompt === this.topic.clientAuthMissing) ? + Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH : + Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH; + aDocShell.displayLoadError(errorCode, failedURI, undefined, undefined); + }); + }, +}; diff --git a/browser/components/onionservices/content/netError/browser.svg b/browser/components/onionservices/content/netError/browser.svg new file mode 100644 index 0000000000000..1359679f71718 --- /dev/null +++ b/browser/components/onionservices/content/netError/browser.svg @@ -0,0 +1,3 @@ +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <path fill="context-fill" fill-opacity="context-fill-opacity" d="m49 6h-37.5c-1.98912 0-3.89678.79018-5.3033 2.1967s-2.1967 3.3142-2.1967 5.3033v33.75c0 1.9891.79018 3.8968 2.1967 5.3033s3.31418 2.1967 5.3033 2.1967h37.5c1.9891 0 3.8968-.7902 5.3033-2.1967s2.1967-3.3142 2.1967-5.3033v-33.75c0-1.9891-.7902-3.89678-2.1967-5.3033s-3.3142-2.1967-5.3033-2.1967zm-38.0625 4.6875h38.625l2.25 2.25v8.0625h-43.125v-8.0625zm38.625 39.375h-38.625l-2.25-2.25v-22.125h43.125v22.125z"/> +</svg> diff --git a/browser/components/onionservices/content/netError/network.svg b/browser/components/onionservices/content/netError/network.svg new file mode 100644 index 0000000000000..68610e30bfcaa --- /dev/null +++ b/browser/components/onionservices/content/netError/network.svg @@ -0,0 +1,3 @@ +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <path fill="context-fill" fill-opacity="context-fill-opacity" d="m30 1.875c-7.4592 0-14.6129 2.96316-19.8874 8.2376-5.27444 5.2745-8.2376 12.4282-8.2376 19.8874s2.96316 14.6129 8.2376 19.8874c5.2745 5.2744 12.4282 8.2376 19.8874 8.2376s14.6129-2.9632 19.8874-8.2376c5.2744-5.2745 8.2376-12.4282 8.2376-19.8874s-2.9632-14.6129-8.2376-19.8874c-5.2745-5.27444-12.4282-8.2376-19.8874-8.2376zm9.1762 6.5625c3.8504 1.6533 7.1876 4.3079 9.6646 7.6877 2.477 3.3799 4.0034 7.3615 4.4205 11.531h-8.35 [...] +</svg> diff --git a/browser/components/onionservices/content/netError/onionNetError.css b/browser/components/onionservices/content/netError/onionNetError.css new file mode 100644 index 0000000000000..2c92b187b71c6 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionNetError.css @@ -0,0 +1,88 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +#onionErrorDiagramContainer { + margin: 0px auto 40px 0px; + /* 3 icons 64px wide each seperated by a 64px gap */ + width: 384px; + display: grid; + grid-row-gap: 15px; + grid-column-gap: 64px; + grid-template-columns: 1fr 1fr 1fr; +} + +#onionErrorDiagramContainer > div { + margin: auto; + position: relative; /* needed to allow overlay of the ok or error icon */ +} + +.onionErrorImage { + width: 64px; + height: 64px; + background-size: 64px 64px; + background-position: center; + background-repeat: no-repeat; + -moz-context-properties: fill; + fill: var(--in-content-icon-color); + opacity: 50%; +} + +/* TODO: remove these --warning-color definitions after we + are esr92 based (tor-browser#40640 */ +.onionErrorImage { + --warning-color: #ffa436; +} + +@media (-moz-toolbar-prefers-color-scheme: dark) { + .onionErrorImage { + --warning-color: #ffbd4f; + } +} + +@media (prefers-contrast) { + .onionErrorImage { + --warning-color: var(--in-content-page-color); + } +} + +.onionErrorImage[status] { + opacity: 100%; +} + +#onionErrorBrowserImage { + background-image: url("browser.svg"); +} + +#onionErrorNetworkImage { + background-image: url("network.svg"); +} + +#onionErrorOnionSiteImage { + background-image: url("onionsite.svg"); +} + +/* rules to support overlay of the ok or error icon */ +.onionErrorImage[status]::after { + content: " "; + position: absolute; + left: -8px; + top: calc((64px - 24px) / 2); + width: 24px; + height: 24px; + -moz-context-properties: fill; + fill: var(--in-content-page-background); + + background-repeat: no-repeat; + background-position: center; + border: 3px solid var(--in-content-page-background); + border-radius: 50%; +} + +.onionErrorImage[status="ok"]::after { + background-color: var(--in-content-icon-color); + background-image: url("chrome://global/skin/icons/check.svg"); +} + +.onionErrorImage[status="error"]::after { + background-color: var(--warning-color); + background-image: url("chrome://global/skin/icons/close.svg"); +} diff --git a/browser/components/onionservices/content/netError/onionNetError.js b/browser/components/onionservices/content/netError/onionNetError.js new file mode 100644 index 0000000000000..745c58ec61243 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionNetError.js @@ -0,0 +1,243 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +/* eslint-env mozilla/frame-script */ + +var OnionServicesAboutNetError = { + _selector: { + textContainer: "div#text-container", + header: ".title-text", + longDesc: "#errorLongDesc", + learnMoreContainer: "#learnMoreContainer", + learnMoreLink: "#learnMoreLink", + contentContainer: "#errorLongContent", + tryAgainButtonContainer: "#netErrorButtonContainer", + }, + _status: { + ok: "ok", + error: "error", + }, + + _diagramInfoMap: undefined, + + // Public functions (called from outside this file). + // + // This initPage() function may need to be updated if the structure of + // browser/base/content/aboutNetError.xhtml changes. Specifically, it + // references the following elements: + // query string parameter e + // class title-text + // id errorLongDesc + // id learnMoreContainer + // id learnMoreLink + // id errorLongContent + initPage(aDoc) { + const searchParams = new URLSearchParams(aDoc.documentURI.split("?")[1]); + const err = searchParams.get("e"); + + const errPrefix = "onionServices."; + const errName = err.substring(errPrefix.length); + + this._strings = RPMGetTorStrings(); + + const stringsObj = this._strings[errName]; + if (!stringsObj) { + return; + } + + this._insertStylesheet(aDoc); + + const pageTitle = stringsObj.pageTitle; + const header = stringsObj.header; + const longDescription = stringsObj.longDescription; // optional + const learnMoreURL = stringsObj.learnMoreURL; + + if (pageTitle) { + aDoc.title = pageTitle; + } + + if (header) { + const headerElem = aDoc.querySelector(this._selector.header); + if (headerElem) { + headerElem.textContent = header; + } + } + + const ld = aDoc.querySelector(this._selector.longDesc); + if (ld) { + if (longDescription) { + const hexErr = this._hexErrorFromName(errName); + ld.textContent = longDescription.replace("%S", hexErr); + } else { + // This onion service error does not have a long description. Since + // it is set to a generic error string by the code in + // browser/base/content/aboutNetError.js, hide it here. + ld.style.display = "none"; + } + } + + if (learnMoreURL) { + const lmContainer = aDoc.querySelector(this._selector.learnMoreContainer); + if (lmContainer) { + lmContainer.style.display = "block"; + } + const lmLink = lmContainer.querySelector(this._selector.learnMoreLink); + if (lmLink) { + lmLink.setAttribute("href", learnMoreURL); + } + } + + // Remove the "Try Again" button if the user made a typo in the .onion + // address since it is not useful in that case. + if (errName === "badAddress") { + const tryAgainButton = aDoc.querySelector( + this._selector.tryAgainButtonContainer + ); + if (tryAgainButton) { + tryAgainButton.style.display = "none"; + } + } + + this._insertDiagram(aDoc, errName); + }, // initPage() + + _insertStylesheet(aDoc) { + const url = + "chrome://browser/content/onionservices/netError/onionNetError.css"; + let linkElem = aDoc.createElement("link"); + linkElem.rel = "stylesheet"; + linkElem.href = url; + linkElem.type = "text/css"; + aDoc.head.appendChild(linkElem); + }, + + _insertDiagram(aDoc, aErrorName) { + // The onion error diagram consists of a grid of div elements. + // The first row contains three images (Browser, Network, Onionsite) and + // the second row contains labels for the images that are in the first row. + // The _diagramInfoMap describes for each type of onion service error + // whether a small ok or error status icon is overlaid on top of the main + // Browser/Network/Onionsite images. + if (!this._diagramInfoMap) { + this._diagramInfoMap = new Map(); + this._diagramInfoMap.set("descNotFound", { + browser: this._status.ok, + network: this._status.ok, + onionSite: this._status.error, + }); + this._diagramInfoMap.set("descInvalid", { + browser: this._status.ok, + network: this._status.error, + }); + this._diagramInfoMap.set("introFailed", { + browser: this._status.ok, + network: this._status.error, + }); + this._diagramInfoMap.set("rendezvousFailed", { + browser: this._status.ok, + network: this._status.error, + }); + this._diagramInfoMap.set("clientAuthMissing", { + browser: this._status.error, + }); + this._diagramInfoMap.set("clientAuthIncorrect", { + browser: this._status.error, + }); + this._diagramInfoMap.set("badAddress", { + browser: this._status.error, + }); + this._diagramInfoMap.set("introTimedOut", { + browser: this._status.ok, + network: this._status.error, + }); + } + + const diagramInfo = this._diagramInfoMap.get(aErrorName); + + const container = this._createDiv(aDoc, "onionErrorDiagramContainer"); + const imageClass = "onionErrorImage"; + + const browserImage = this._createDiv( + aDoc, + "onionErrorBrowserImage", + imageClass, + container + ); + if (diagramInfo && diagramInfo.browser) { + browserImage.setAttribute("status", diagramInfo.browser); + } + + const networkImage = this._createDiv( + aDoc, + "onionErrorNetworkImage", + imageClass, + container + ); + if (diagramInfo && diagramInfo.network) { + networkImage.setAttribute("status", diagramInfo.network); + } + + const onionSiteImage = this._createDiv( + aDoc, + "onionErrorOnionSiteImage", + imageClass, + container + ); + if (diagramInfo && diagramInfo.onionSite) { + onionSiteImage.setAttribute("status", diagramInfo.onionSite); + } + + let labelDiv = this._createDiv(aDoc, undefined, undefined, container); + labelDiv.textContent = this._strings.errorPage.browser; + labelDiv = this._createDiv(aDoc, undefined, undefined, container); + labelDiv.textContent = this._strings.errorPage.network; + labelDiv = this._createDiv(aDoc, undefined, undefined, container); + labelDiv.textContent = this._strings.errorPage.onionSite; + + const textContainer = aDoc.querySelector( + this._selector.textContainer + ); + textContainer?.insertBefore(container, textContainer.firstChild); + }, // _insertDiagram() + + _createDiv(aDoc, aID, aClass, aParentElem) { + const div = aDoc.createElement("div"); + if (aID) { + div.id = aID; + } + if (aClass) { + div.setAttribute("class", aClass); + } + if (aParentElem) { + aParentElem.appendChild(div); + } + + return div; + }, + + _hexErrorFromName(aErrorName) { + // We do not have access to the original Tor SOCKS error code here, so + // perform a reverse mapping from the error name. + switch (aErrorName) { + case "descNotFound": + return "0xF0"; + case "descInvalid": + return "0xF1"; + case "introFailed": + return "0xF2"; + case "rendezvousFailed": + return "0xF3"; + case "clientAuthMissing": + return "0xF4"; + case "clientAuthIncorrect": + return "0xF5"; + case "badAddress": + return "0xF6"; + case "introTimedOut": + return "0xF7"; + } + + return ""; + }, +}; diff --git a/browser/components/onionservices/content/netError/onionsite.svg b/browser/components/onionservices/content/netError/onionsite.svg new file mode 100644 index 0000000000000..c1b2d7382dc9a --- /dev/null +++ b/browser/components/onionservices/content/netError/onionsite.svg @@ -0,0 +1,8 @@ +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <g fill="context-fill" fill-opacity="context-fill-opacity"> + <path clip-rule="evenodd" d="m11.25 6h37.5c1.9891 0 3.8968.79018 5.3033 2.1967s2.1967 3.3142 2.1967 5.3033v33.75c0 1.9891-.7902 3.8968-2.1967 5.3033s-3.3142 2.1967-5.3033 2.1967h-37.5c-1.98912 0-3.89678-.7902-5.3033-2.1967s-2.1967-3.3142-2.1967-5.3033v-33.75c0-1.9891.79018-3.89678 2.1967-5.3033s3.31418-2.1967 5.3033-2.1967zm-.5625 4.6875h38.625l2.25 2.25v34.875l-2.25 2.25h-38.625l-2.25-2.25v-34.875z" fill-rule="evenodd"/> + <path d="m15.9606 22c-.52 0-1.0187-.2107-1.3863-.5858-.3677-.3751-.5743-.8838-.5743-1.4142s.2066-1.0391.5743-1.4142c.3676-.3751.8663-.5858 1.3863-.5858h14.0788c.52 0 1.0187.2107 1.3863.5858.3677.3751.5743.8838.5743 1.4142s-.2066 1.0391-.5743 1.4142c-.3676.3751-.8663.5858-1.3863.5858z"/> + <path d="m44.0709 32h-28.1418c-.5116 0-1.0023-.2107-1.3641-.5858s-.565-.8838-.565-1.4142.2032-1.0391.565-1.4142.8525-.5858 1.3641-.5858h28.1418c.5116 0 1.0023.2107 1.3641.5858s.565.8838.565 1.4142-.2032 1.0391-.565 1.4142-.8525.5858-1.3641.5858z"/> + <path d="m44.0709 42h-28.1418c-.5116 0-1.0023-.2107-1.3641-.5858s-.565-.8838-.565-1.4142.2032-1.0391.565-1.4142.8525-.5858 1.3641-.5858h28.1418c.5116 0 1.0023.2107 1.3641.5858s.565.8838.565 1.4142-.2032 1.0391-.565 1.4142-.8525.5858-1.3641.5858z"/> + </g> +</svg> diff --git a/browser/components/onionservices/content/onionservices.css b/browser/components/onionservices/content/onionservices.css new file mode 100644 index 0000000000000..e2621ec8266de --- /dev/null +++ b/browser/components/onionservices/content/onionservices.css @@ -0,0 +1,69 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +@namespace html url("http://www.w3.org/1999/xhtml"); + +html|*#tor-clientauth-notification-onionname { + font-weight: bold; +} + +html|*#tor-clientauth-notification-key { + box-sizing: border-box; + width: 100%; + margin-top: 15px; + padding: 6px; +} + +/* Start of rules adapted from + * browser/components/newtab/css/activity-stream-mac.css (linux and windows + * use the same rules). + */ +html|*#tor-clientauth-notification-key.invalid { + border: 1px solid #D70022; + box-shadow: 0 0 0 1px #D70022, 0 0 0 4px rgba(215, 0, 34, 0.3); +} + +html|*#tor-clientauth-warning { + display: inline-block; + animation: fade-up-tt 450ms; + background: #D70022; + border-radius: 2px; + color: #FFF; + inset-inline-start: 3px; + padding: 5px 12px; + position: relative; + top: 6px; + z-index: 1; +} + +html|*#tor-clientauth-warning[hidden] { + display: none; +} + +html|*#tor-clientauth-warning::before { + background: #D70022; + bottom: -8px; + content: '.'; + height: 16px; + inset-inline-start: 12px; + position: absolute; + text-indent: -999px; + top: -7px; + transform: rotate(45deg); + white-space: nowrap; + width: 16px; + z-index: -1; +} + +@keyframes fade-up-tt { + 0% { + opacity: 0; + transform: translateY(15px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} +/* End of rules adapted from + * browser/components/newtab/css/activity-stream-mac.css + */ diff --git a/browser/components/onionservices/content/savedKeysDialog.js b/browser/components/onionservices/content/savedKeysDialog.js new file mode 100644 index 0000000000000..b1376bbabe85c --- /dev/null +++ b/browser/components/onionservices/content/savedKeysDialog.js @@ -0,0 +1,259 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +ChromeUtils.defineModuleGetter( + this, + "controller", + "resource://torbutton/modules/tor-control-port.js" +); + +var gOnionServicesSavedKeysDialog = { + selector: { + dialog: "#onionservices-savedkeys-dialog", + intro: "#onionservices-savedkeys-intro", + tree: "#onionservices-savedkeys-tree", + onionSiteCol: "#onionservices-savedkeys-siteCol", + onionKeyCol: "#onionservices-savedkeys-keyCol", + errorIcon: "#onionservices-savedkeys-errorIcon", + errorMessage: "#onionservices-savedkeys-errorMessage", + removeButton: "#onionservices-savedkeys-remove", + removeAllButton: "#onionservices-savedkeys-removeall", + }, + + _tree: undefined, + _isBusy: false, // true when loading data, deleting a key, etc. + + // Public functions (called from outside this file). + async deleteSelectedKeys() { + this._setBusyState(true); + + const indexesToDelete = []; + const count = this._tree.view.selection.getRangeCount(); + for (let i = 0; i < count; ++i) { + const minObj = {}; + const maxObj = {}; + this._tree.view.selection.getRangeAt(i, minObj, maxObj); + for (let idx = minObj.value; idx <= maxObj.value; ++idx) { + indexesToDelete.push(idx); + } + } + + if (indexesToDelete.length > 0) { + const controllerFailureMsg = + TorStrings.onionServices.authPreferences.failedToRemoveKey; + try { + const torController = controller(aError => { + this._showError(controllerFailureMsg); + }); + + // Remove in reverse index order to avoid issues caused by index changes. + for (let i = indexesToDelete.length - 1; i >= 0; --i) { + await this._deleteOneKey(torController, indexesToDelete[i]); + } + } catch (e) { + if (e.torMessage) { + this._showError(e.torMessage); + } else { + this._showError(controllerFailureMsg); + } + } + } + + this._setBusyState(false); + }, + + async deleteAllKeys() { + this._tree.view.selection.selectAll(); + await this.deleteSelectedKeys(); + }, + + updateButtonsState() { + const haveSelection = this._tree.view.selection.getRangeCount() > 0; + const dialog = document.querySelector(this.selector.dialog); + const removeSelectedBtn = dialog.querySelector(this.selector.removeButton); + removeSelectedBtn.disabled = this._isBusy || !haveSelection; + const removeAllBtn = dialog.querySelector(this.selector.removeAllButton); + removeAllBtn.disabled = this._isBusy || this.rowCount === 0; + }, + + // Private functions. + _onLoad() { + document.mozSubdialogReady = this._init(); + }, + + async _init() { + await this._populateXUL(); + + window.addEventListener("keypress", this._onWindowKeyPress.bind(this)); + + // We don't use await here because we want _loadSavedKeys() to run + // in the background and not block loading of this dialog. + this._loadSavedKeys(); + }, + + async _populateXUL() { + const dialog = document.querySelector(this.selector.dialog); + const authPrefStrings = TorStrings.onionServices.authPreferences; + dialog.setAttribute("title", authPrefStrings.dialogTitle); + + let elem = dialog.querySelector(this.selector.intro); + elem.textContent = authPrefStrings.dialogIntro; + + elem = dialog.querySelector(this.selector.onionSiteCol); + elem.setAttribute("label", authPrefStrings.onionSite); + + elem = dialog.querySelector(this.selector.onionKeyCol); + elem.setAttribute("label", authPrefStrings.onionKey); + + elem = dialog.querySelector(this.selector.removeButton); + elem.setAttribute("label", authPrefStrings.remove); + + elem = dialog.querySelector(this.selector.removeAllButton); + elem.setAttribute("label", authPrefStrings.removeAll); + + this._tree = dialog.querySelector(this.selector.tree); + }, + + async _loadSavedKeys() { + const controllerFailureMsg = + TorStrings.onionServices.authPreferences.failedToGetKeys; + this._setBusyState(true); + + try { + this._tree.view = this; + + const torController = controller(aError => { + this._showError(controllerFailureMsg); + }); + + const keyInfoList = await torController.onionAuthViewKeys(); + if (keyInfoList) { + // Filter out temporary keys. + this._keyInfoList = keyInfoList.filter(aKeyInfo => { + if (!aKeyInfo.Flags) { + return false; + } + + const flags = aKeyInfo.Flags.split(","); + return flags.includes("Permanent"); + }); + + // Sort by the .onion address. + this._keyInfoList.sort((aObj1, aObj2) => { + const hsAddr1 = aObj1.hsAddress.toLowerCase(); + const hsAddr2 = aObj2.hsAddress.toLowerCase(); + if (hsAddr1 < hsAddr2) { + return -1; + } + return hsAddr1 > hsAddr2 ? 1 : 0; + }); + } + + // Render the tree content. + this._tree.rowCountChanged(0, this.rowCount); + } catch (e) { + if (e.torMessage) { + this._showError(e.torMessage); + } else { + this._showError(controllerFailureMsg); + } + } + + this._setBusyState(false); + }, + + // This method may throw; callers should catch errors. + async _deleteOneKey(aTorController, aIndex) { + const keyInfoObj = this._keyInfoList[aIndex]; + await aTorController.onionAuthRemove(keyInfoObj.hsAddress); + this._tree.view.selection.clearRange(aIndex, aIndex); + this._keyInfoList.splice(aIndex, 1); + this._tree.rowCountChanged(aIndex + 1, -1); + }, + + _setBusyState(aIsBusy) { + this._isBusy = aIsBusy; + this.updateButtonsState(); + }, + + _onWindowKeyPress(event) { + if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { + window.close(); + } else if (event.keyCode === KeyEvent.DOM_VK_DELETE) { + this.deleteSelectedKeys(); + } + }, + + _showError(aMessage) { + const dialog = document.querySelector(this.selector.dialog); + const errorIcon = dialog.querySelector(this.selector.errorIcon); + errorIcon.style.visibility = aMessage ? "visible" : "hidden"; + const errorDesc = dialog.querySelector(this.selector.errorMessage); + errorDesc.textContent = aMessage ? aMessage : ""; + }, + + // XUL tree widget view implementation. + get rowCount() { + return this._keyInfoList ? this._keyInfoList.length : 0; + }, + + getCellText(aRow, aCol) { + let val = ""; + if (this._keyInfoList && aRow < this._keyInfoList.length) { + const keyInfo = this._keyInfoList[aRow]; + if (aCol.id.endsWith("-siteCol")) { + val = keyInfo.hsAddress; + } else if (aCol.id.endsWith("-keyCol")) { + val = keyInfo.typeAndKey; + // Omit keyType because it is always "x25519". + const idx = val.indexOf(":"); + if (idx > 0) { + val = val.substring(idx + 1); + } + } + } + + return val; + }, + + isSeparator(index) { + return false; + }, + + isSorted() { + return false; + }, + + isContainer(index) { + return false; + }, + + setTree(tree) {}, + + getImageSrc(row, column) {}, + + getCellValue(row, column) {}, + + cycleHeader(column) {}, + + getRowProperties(row) { + return ""; + }, + + getColumnProperties(column) { + return ""; + }, + + getCellProperties(row, column) { + return ""; + }, +}; + +window.addEventListener("load", () => gOnionServicesSavedKeysDialog._onLoad()); diff --git a/browser/components/onionservices/content/savedKeysDialog.xhtml b/browser/components/onionservices/content/savedKeysDialog.xhtml new file mode 100644 index 0000000000000..3db9bb05ea82f --- /dev/null +++ b/browser/components/onionservices/content/savedKeysDialog.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!-- Copyright (c) 2020, The Tor Project, Inc. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css" type="text/css"?> + +<window id="onionservices-savedkeys-dialog" + windowtype="OnionServices:SavedKeys" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 45em;"> + + <script src="chrome://browser/content/onionservices/savedKeysDialog.js"/> + + <vbox id="onionservices-savedkeys" class="contentPane" flex="1"> + <label id="onionservices-savedkeys-intro" + control="onionservices-savedkeys-tree"/> + <separator class="thin"/> + <tree id="onionservices-savedkeys-tree" flex="1" hidecolumnpicker="true" + width="750" + style="height: 20em;" + onselect="gOnionServicesSavedKeysDialog.updateButtonsState();"> + <treecols> + <treecol id="onionservices-savedkeys-siteCol" flex="1" persist="width"/> + <splitter class="tree-splitter"/> + <treecol id="onionservices-savedkeys-keyCol" flex="1" persist="width"/> + </treecols> + <treechildren/> + </tree> + <hbox id="onionservices-savedkeys-errorContainer" align="baseline" flex="1"> + <image id="onionservices-savedkeys-errorIcon"/> + <description id="onionservices-savedkeys-errorMessage" flex="1"/> + </hbox> + <separator class="thin"/> + <hbox id="onionservices-savedkeys-buttons"> + <button id="onionservices-savedkeys-remove" disabled="true" + oncommand="gOnionServicesSavedKeysDialog.deleteSelectedKeys();"/> + <button id="onionservices-savedkeys-removeall" + oncommand="gOnionServicesSavedKeysDialog.deleteAllKeys();"/> + </hbox> + </vbox> +</window> diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn new file mode 100644 index 0000000000000..9d6ce88d18416 --- /dev/null +++ b/browser/components/onionservices/jar.mn @@ -0,0 +1,9 @@ +browser.jar: + content/browser/onionservices/authPreferences.css (content/authPreferences.css) + content/browser/onionservices/authPreferences.js (content/authPreferences.js) + content/browser/onionservices/authPrompt.js (content/authPrompt.js) + content/browser/onionservices/authUtil.jsm (content/authUtil.jsm) + content/browser/onionservices/netError/ (content/netError/*) + content/browser/onionservices/onionservices.css (content/onionservices.css) + content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js) + content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml) diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build new file mode 100644 index 0000000000000..2661ad7cb9f3d --- /dev/null +++ b/browser/components/onionservices/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 48836ec54a4e0..30ce70079adb7 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -13,6 +13,7 @@ <?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css"?> <?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?> <?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index d2cf2ea9c89c7..18d01fee6b339 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -505,6 +505,8 @@ <label id="fips-desc" hidden="true" data-l10n-id="forms-master-pw-fips-desc"></label> </groupbox>
+#include ../onionservices/content/authPreferences.inc.xhtml + <!-- The form autofill section is inserted in to this box after the form autofill extension has initialized. --> <groupbox id="formAutofillGroupBox" diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js index bce7bb7e8a9c6..932d4291e4862 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -80,6 +80,12 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() { } });
+XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionServicesAuthPreferences"], + "chrome://browser/content/onionservices/authPreferences.js" +); + // TODO: module import via ChromeUtils.defineModuleGetter XPCOMUtils.defineLazyScriptGetter( this, @@ -522,6 +528,7 @@ var gPrivacyPane = { this.trackingProtectionReadPrefs(); this.networkCookieBehaviorReadPrefs(); this._initTrackingProtectionExtensionControl(); + OnionServicesAuthPreferences.init(); this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true); diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css index 658fa7f7430ad..67dd640baf162 100644 --- a/browser/themes/shared/notification-icons.inc.css +++ b/browser/themes/shared/notification-icons.inc.css @@ -137,6 +137,9 @@ list-style-image: url(chrome://browser/skin/notification-icons/persistent-storage.svg); }
+/* Reuse Firefox's login (key) icon for the Tor onion services auth. prompt */ +.popup-notification-icon[popupid="tor-clientauth"], +.tor-clientauth-icon, .popup-notification-icon[popupid="password"], .login-icon { list-style-image: url(chrome://browser/skin/login.svg); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 67732b7cad675..ced1ff2bf81d3 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -3589,6 +3589,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, } } else { // Errors requiring simple formatting + bool isOnionAuthError = false; switch (aError) { case NS_ERROR_MALFORMED_URI: // URI is malformed @@ -3671,10 +3672,44 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, // HTTP/2 or HTTP/3 stack detected a protocol error error = "networkProtocolError"; break; - + case NS_ERROR_TOR_ONION_SVC_NOT_FOUND: + error = "onionServices.descNotFound"; + break; + case NS_ERROR_TOR_ONION_SVC_IS_INVALID: + error = "onionServices.descInvalid"; + break; + case NS_ERROR_TOR_ONION_SVC_INTRO_FAILED: + error = "onionServices.introFailed"; + break; + case NS_ERROR_TOR_ONION_SVC_REND_FAILED: + error = "onionServices.rendezvousFailed"; + break; + case NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH: + error = "onionServices.clientAuthMissing"; + isOnionAuthError = true; + break; + case NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH: + error = "onionServices.clientAuthIncorrect"; + isOnionAuthError = true; + break; + case NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS: + error = "onionServices.badAddress"; + break; + case NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT: + error = "onionServices.introTimedOut"; + break; default: break; } + + // The presence of aFailedChannel indicates that we arrived here due to a + // failed connection attempt. Note that we will arrive here a second time + // if the user cancels the Tor client auth prompt, but in that case we + // will not have a failed channel and therefore we will not prompt again. + if (isOnionAuthError && aFailedChannel) { + // Display about:blank while the Tor client auth prompt is open. + errorPage.AssignLiteral("blank"); + } }
// If the HTTPS-Only Mode upgraded this request and the upgrade might have @@ -3757,6 +3792,20 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, nsAutoString str; rv = stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str); + if (NS_FAILED(rv)) { + // As a fallback, check torbutton.properties for the error string. + const char bundleURL[] = "chrome://torbutton/locale/torbutton.properties"; + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + if (stringBundleService) { + nsCOMPtr<nsIStringBundle> tbStringBundle; + if (NS_SUCCEEDED(stringBundleService->CreateBundle( + bundleURL, getter_AddRefs(tbStringBundle)))) { + rv = tbStringBundle->FormatStringFromName(errorDescriptionID, + formatStrs, str); + } + } + } NS_ENSURE_SUCCESS(rv, rv); messageStr.Assign(str); } @@ -6175,6 +6224,7 @@ nsresult nsDocShell::FilterStatusForErrorPage( aStatus == NS_ERROR_FILE_ACCESS_DENIED || aStatus == NS_ERROR_CORRUPTED_CONTENT || aStatus == NS_ERROR_INVALID_CONTENT_ENCODING || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_TOR || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { // Errors to be shown for any frame return aStatus; @@ -7898,6 +7948,35 @@ nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType, FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags); }
+ // Arrange to show a Tor onion service client authentication prompt if + // appropriate. + if ((mLoadType == LOAD_ERROR_PAGE) && failedChannel) { + nsresult status = NS_OK; + if (NS_SUCCEEDED(failedChannel->GetStatus(&status)) && + ((status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH) || + (status == NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH))) { + nsAutoCString onionHost; + failedURI->GetHost(onionHost); + const char* topic = (status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH) + ? "tor-onion-services-clientauth-missing" + : "tor-onion-services-clientauth-incorrect"; + if (XRE_IsContentProcess()) { + nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild(); + if (browserChild) { + static_cast<BrowserChild*>(browserChild.get()) + ->SendShowOnionServicesAuthPrompt(onionHost, nsCString(topic)); + } + } else { + nsCOMPtr<nsPIDOMWindowOuter> browserWin = GetWindow(); + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + if (browserWin && obsSvc) { + obsSvc->NotifyObservers(browserWin, topic, + NS_ConvertUTF8toUTF16(onionHost).get()); + } + } + } + } + return NS_OK; }
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 05d77937f986e..4145111ae8490 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -3810,6 +3810,27 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt( return IPC_OK(); }
+mozilla::ipc::IPCResult BrowserParent::RecvShowOnionServicesAuthPrompt( + const nsCString& aOnionName, const nsCString& aTopic) { + nsCOMPtr<nsIBrowser> browser = + mFrameElement ? mFrameElement->AsBrowser() : nullptr; + if (!browser) { + // If the tab is being closed, the browser may not be available. + // In this case we can ignore the request. + return IPC_OK(); + } + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (!os) { + return IPC_FAIL_NO_REASON(this); + } + nsresult rv = os->NotifyObservers(browser, aTopic.get(), + NS_ConvertUTF8toUTF16(aOnionName).get()); + if (NS_FAILED(rv)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + mozilla::ipc::IPCResult BrowserParent::RecvVisitURI(nsIURI* aURI, nsIURI* aLastVisitedURI, const uint32_t& aFlags) { diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h index 80e4d055e26c0..a36ebfc8ca052 100644 --- a/dom/ipc/BrowserParent.h +++ b/dom/ipc/BrowserParent.h @@ -736,6 +736,9 @@ class BrowserParent final : public PBrowserParent, mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt( const nsCString& aOrigin, const bool& aHideDoorHanger);
+ mozilla::ipc::IPCResult RecvShowOnionServicesAuthPrompt( + const nsCString& aOnionName, const nsCString& aTopic); + mozilla::ipc::IPCResult RecvSetSystemFont(const nsCString& aFontName); mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName);
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 9750219fa46ac..5706c7f5da003 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -577,6 +577,15 @@ parent: async RequestPointerCapture(uint32_t aPointerId) returns (bool aSuccess); async ReleasePointerCapture(uint32_t aPointerId);
+ /** + * This function is used to notify the parent that it should display a + * onion services client authentication prompt. + * + * @param aOnionHost The hostname of the .onion that needs authentication. + * @param aTopic The reason for the prompt. + */ + async ShowOnionServicesAuthPrompt(nsCString aOnionHost, nsCString aTopic); + child: async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse); async UpdateEpoch(uint32_t aEpoch); diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg index c7fbdd23f3784..07f529957bd05 100644 --- a/js/xpconnect/src/xpc.msg +++ b/js/xpconnect/src/xpc.msg @@ -248,5 +248,15 @@ XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinti XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining") XPC_MSG_DEF(NS_ERROR_SOCIALTRACKING_URI , "The URI is social tracking")
+/* Codes related to Tor */ +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_NOT_FOUND , "Tor onion service descriptor cannot be found") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_IS_INVALID , "Tor onion service descriptor is invalid") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED , "Tor onion service introduction failed") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_REND_FAILED , "Tor onion service rendezvous failed") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH, "Tor onion service missing client authorization") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH , "Tor onion service wrong client authorization") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS , "Tor onion service bad address") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT , "Tor onion service introduction timed out") + /* Profile manager error codes */ XPC_MSG_DEF(NS_ERROR_DATABASE_CHANGED , "Flushing the profiles to disk would have overwritten changes made elsewhere.") diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp index 8f44441e1fd0e..99a6f3b60ac30 100644 --- a/netwerk/base/nsSocketTransport2.cpp +++ b/netwerk/base/nsSocketTransport2.cpp @@ -216,6 +216,12 @@ nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) { default: if (psm::IsNSSErrorCode(errorCode)) { rv = psm::GetXPCOMFromNSSError(errorCode); + } else { + // If we received a Tor extended error code via SOCKS, pass it through. + nsresult res = nsresult(errorCode); + if (NS_ERROR_GET_MODULE(res) == NS_ERROR_MODULE_TOR) { + rv = res; + } } break;
diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp index 119a3cbf4c510..f9fc29552aceb 100644 --- a/netwerk/socket/nsSOCKSIOLayer.cpp +++ b/netwerk/socket/nsSOCKSIOLayer.cpp @@ -979,6 +979,55 @@ PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() { "08, Address type not supported.")); c = PR_BAD_ADDRESS_ERROR; break; + case 0xF0: // Tor SOCKS5_HS_NOT_FOUND + LOGERROR( + ("socks5: connect failed: F0," + " Tor onion service descriptor can not be found.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_NOT_FOUND); + break; + case 0xF1: // Tor SOCKS5_HS_IS_INVALID + LOGERROR( + ("socks5: connect failed: F1," + " Tor onion service descriptor is invalid.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_IS_INVALID); + break; + case 0xF2: // Tor SOCKS5_HS_INTRO_FAILED + LOGERROR( + ("socks5: connect failed: F2," + " Tor onion service introduction failed.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED); + break; + case 0xF3: // Tor SOCKS5_HS_REND_FAILED + LOGERROR( + ("socks5: connect failed: F3," + " Tor onion service rendezvous failed.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_REND_FAILED); + break; + case 0xF4: // Tor SOCKS5_HS_MISSING_CLIENT_AUTH + LOGERROR( + ("socks5: connect failed: F4," + " Tor onion service missing client authorization.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH); + break; + case 0xF5: // Tor SOCKS5_HS_BAD_CLIENT_AUTH + LOGERROR( + ("socks5: connect failed: F5," + " Tor onion service wrong client authorization.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH); + break; + case 0xF6: // Tor SOCKS5_HS_BAD_ADDRESS + LOGERROR( + ("socks5: connect failed: F6," + " Tor onion service bad address.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS); + break; + case 0xF7: // Tor SOCKS5_HS_INTRO_TIMEDOUT + LOGERROR( + ("socks5: connect failed: F7," + " Tor onion service introduction timed out.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT); + break; + default: LOGERROR(("socks5: connect failed.")); break; diff --git a/toolkit/modules/PopupNotifications.jsm b/toolkit/modules/PopupNotifications.jsm index d6518723afab3..9764cfd496c3a 100644 --- a/toolkit/modules/PopupNotifications.jsm +++ b/toolkit/modules/PopupNotifications.jsm @@ -410,6 +410,8 @@ PopupNotifications.prototype = { * will be dismissed instead of removed after running the callback. * - [optional] disabled (boolean): If this is true, the button * will be disabled. + * - [optional] leaveOpen (boolean): If this is true, the notification + * will not be removed after running the callback. * - [optional] disableHighlight (boolean): If this is true, the button * will not apply the default highlight style. * If null, the notification will have a default "OK" action button @@ -1916,6 +1918,10 @@ PopupNotifications.prototype = { this._dismiss(); return; } + + if (action.leaveOpen) { + return; + } }
this._remove(notification); diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index 73c48281276b9..c195ab688b236 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -103,6 +103,7 @@ let RemotePageAccessManager = { RPMAddToHistogram: ["*"], RPMGetInnerMostURI: ["*"], RPMGetHttpResponseHeader: ["*"], + RPMGetTorStrings: ["*"], RPMSendQuery: ["ShouldShowTorConnect"], }, "about:plugins": { diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js index 15c15615ad97c..57458ba0bf5e1 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js @@ -41,5 +41,6 @@ module.exports = { RPMGetHttpResponseHeader: false, RPMTryPingSecureWWWLink: false, RPMOpenSecureWWWLink: false, + RPMGetTorStrings: false, }, }; diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py index c22c27be8546a..8fbcc7f663df9 100755 --- a/xpcom/base/ErrorList.py +++ b/xpcom/base/ErrorList.py @@ -89,6 +89,7 @@ modules["ERRORRESULT"] = Mod(43) # Win32 system error codes, which are not mapped to a specific other value, # see Bug 1686041. modules["WIN32"] = Mod(44) +modules["TOR"] = Mod(45)
# NS_ERROR_MODULE_GENERAL should be used by modules that do not # care if return code values overlap. Callers of methods that @@ -1181,6 +1182,27 @@ with modules["ERRORRESULT"]: errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5)
+# ======================================================================= +# 45: Tor-specific error codes. +# ======================================================================= +with modules["TOR"]: + # Tor onion service descriptor can not be found. + errors["NS_ERROR_TOR_ONION_SVC_NOT_FOUND"] = FAILURE(1) + # Tor onion service descriptor is invalid. + errors["NS_ERROR_TOR_ONION_SVC_IS_INVALID"] = FAILURE(2) + # Tor onion service introduction failed. + errors["NS_ERROR_TOR_ONION_SVC_INTRO_FAILED"] = FAILURE(3) + # Tor onion service rendezvous failed. + errors["NS_ERROR_TOR_ONION_SVC_REND_FAILED"] = FAILURE(4) + # Tor onion service missing client authorization. + errors["NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH"] = FAILURE(5) + # Tor onion service wrong client authorization. + errors["NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH"] = FAILURE(6) + # Tor onion service bad address. + errors["NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS"] = FAILURE(7) + # Tor onion service introduction timed out. + errors["NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT"] = FAILURE(8) + # ======================================================================= # 51: NS_ERROR_MODULE_GENERAL # =======================================================================
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 0edd5ddc3a36baa95a6ad776d9a029e8ad2167a9 Author: Alex Catarineu acat@torproject.org AuthorDate: Thu Feb 13 13:24:33 2020 +0100
Bug 28005: Implement .onion alias urlbar rewrites
The old patch to support .tor.onion hosts was implemented through HTTPS-Everywhere. Now that the extension has been deprecated (or is going to soon), we have a new implementation, so we need only the code that interacts with HTTPS-Everywhere, which we use to remove all SecureDrop channels, rather than adding the 2021 one.
When we stop shipping HTTPS-Everywhere, we will be able to remove this patch, and keep only the one for #40458. --- .../onionservices/ExtensionMessaging.jsm | 77 +++++++++++++ .../onionservices/HttpsEverywhereControl.jsm | 127 +++++++++++++++++++++ browser/components/onionservices/moz.build | 6 + 3 files changed, 210 insertions(+)
diff --git a/browser/components/onionservices/ExtensionMessaging.jsm b/browser/components/onionservices/ExtensionMessaging.jsm new file mode 100644 index 0000000000000..c93b8c6edf85b --- /dev/null +++ b/browser/components/onionservices/ExtensionMessaging.jsm @@ -0,0 +1,77 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["ExtensionMessaging"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { ExtensionUtils } = ChromeUtils.import( + "resource://gre/modules/ExtensionUtils.jsm" +); +const { MessageChannel } = ChromeUtils.import( + "resource://gre/modules/MessageChannel.jsm" +); +const { AddonManager } = ChromeUtils.import( + "resource://gre/modules/AddonManager.jsm" +); + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + ExtensionParent: "resource://gre/modules/ExtensionParent.jsm", +}); + +class ExtensionMessaging { + constructor() { + this._callback = null; + this._handlers = new Map(); + this._messageManager = Services.cpmm; + } + + async sendMessage(message, extensionId) { + const addon = await AddonManager.getAddonByID(extensionId); + if (!addon) { + throw new Error(`extension '${extensionId} does not exist`); + } + await addon.startupPromise; + + const { torSendExtensionMessage } = ExtensionParent; + return torSendExtensionMessage(extensionId, message); + } + + unload() { + if (this._callback) { + this._handlers.clear(); + this._messageManager.removeMessageListener( + "MessageChannel:Response", + this._callback + ); + this._callback = null; + } + } + + _onMessage({ data }) { + const channelId = data.messageName; + if (this._handlers.has(channelId)) { + const { resolve, reject } = this._handlers.get(channelId); + this._handlers.delete(channelId); + if (data.error) { + reject(new Error(data.error.message)); + } else { + resolve(data.value); + } + } + } + + _init() { + if (this._callback === null) { + this._callback = this._onMessage.bind(this); + this._messageManager.addMessageListener( + "MessageChannel:Response", + this._callback + ); + } + } +} diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm new file mode 100644 index 0000000000000..9e14b7d88b501 --- /dev/null +++ b/browser/components/onionservices/HttpsEverywhereControl.jsm @@ -0,0 +1,127 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"]; + +const { ExtensionMessaging } = ChromeUtils.import( + "resource:///modules/ExtensionMessaging.jsm" +); +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); + +const EXTENSION_ID = "https-everywhere-eff@eff.org"; +const SECUREDROP_TOR_ONION_CHANNEL_2020 = { + name: "SecureDropTorOnion", + jwk: { + kty: "RSA", + e: "AQAB", + n: + "p10BbUVc5Xj2S_-MH3bACNBaISo_r9e3PVPyTTjsGsdg2qSXvqUO42fBtpFAy0zUzIGS83v4JjiRdvKJaZTIvbC8AcpymzdsTqujMm8RPTSy3hO_8mXzGa4DEsIB1uNLnUWRBKXvSGCmT9kFyxhTpkYqokNBzafVihTU34tN2Md1xFHnmZGqfYtPtbJLWAa5Z1M11EyR4lIyUxIiPTV9t1XstDbWr3iS83REJrGEFmjG1-BAgx8_lDUTa41799N2yYEhgZud7bL0M3ei8s5OERjiion5uANkUV3-s2QqUZjiVA-XR_HizXjciaUWNd683KqekpNOZ_0STh_UGwpcwU-KwG07QyiCrLrRpz8S_vH8CqGrrcWY3GSzYe9dp34jJdO65oA-G8tK6fMXtvTCFDZI6oNNaXJH71F5J0YbqO2ZqwKYc2WSi0gKVl2wd9roOVjaBmkJqvocntYuNM7t38fDEWHn5KUkmrTbi [...] + }, + update_path_prefix: "https://securedrop.org/https-everywhere/", + scope: + "^https?:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.securedrop\.tor\.onion\/", + replaces_default_rulesets: false, +}; + +const SECUREDROP_TOR_ONION_CHANNEL = { + name: "SecureDropTorOnion2021", + jwk: { + kty: "RSA", + e: "AQAB", + n: + "vsC7BNafkRe8Uh1DUgCkv6RbPQMdJgAKKnWdSqQd7tQzU1mXfmo_k1Py_2MYMZXOWmqSZ9iwIYkykZYywJ2VyMGve4byj1sLn6YQoOkG8g5Z3V4y0S2RpEfmYumNjTzfq8nxtLnwjaYd4sCUd5wa0SzeLrpRQuXo2bF3QuUF2xcbLJloxX1MmlsMMCdBc-qGNonLJ7bpn_JuyXlDWy1Fkeyw1qgjiOdiRIbMC1x302zgzX6dSrBrNB8Cpsh-vCE0ZjUo8M9caEv06F6QbYmdGJHM0ZZY34OHMSNdf-_qUKIV_SuxuSuFE99tkAeWnbWpyI1V-xhVo1sc7NzChP8ci2TdPvI3_0JyAuCvL6zIFqJUJkZibEUghhg6F09-oNJKpy7rhUJq7zZyLXJsvuXnn0gnIxfjRvMcDfZAKUVMZKRdw7fwWzwQril4Ib0MQOVda9vb_4JMk7Gup-TUI4sfuS4NKwsnKoODIO-2U [...] + }, + update_path_prefix: "https://securedrop.org/https-everywhere-2021/", + scope: + "^https?:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.securedrop\.tor\.onion\/", + replaces_default_rulesets: false, +}; + +class HttpsEverywhereControl { + constructor() { + this._extensionMessaging = null; + this._init(); + } + + async _sendMessage(type, object) { + return this._extensionMessaging.sendMessage( + { + type, + object, + }, + EXTENSION_ID + ); + } + + static async wait(seconds = 1) { + return new Promise(resolve => setTimeout(resolve, seconds * 1000)); + } + + /** + * Uninstalls old .tor.onion update channels from https-everywhere + */ + async uninstallTorOnionUpdateChannel(retries = 5) { + + // TODO: https-everywhere store is initialized asynchronously, so sending a message + // immediately results in a `store.get is undefined` error. + // For now, let's wait a bit and retry a few times if there is an error, but perhaps + // we could suggest https-everywhere to send a message when that happens and listen + // for that here. + await HttpsEverywhereControl.wait(); + + // We now handle .tor.onion domains with our first-party component, so we + // remove known rules from HTTPS-Everywhere. + + try { + await this._sendMessage( + "delete_update_channel", + SECUREDROP_TOR_ONION_CHANNEL_2020.name + ); + } catch (e) { + if (retries <= 0) { + console.warn("Cannot uninstall the SecureDropTorOnion 2020 channel", e); + throw new Error("Could not uninstall the SecureDropTorOnion update channel"); + } + await this.uninstallTorOnionUpdateChannel(retries - 1); + return; + } + try { + await this._sendMessage( + "delete_update_channel", + SECUREDROP_TOR_ONION_CHANNEL.name + ); + } catch (e) { + if (retries <= 0) { + console.warn("Cannot uninstall the SecureDropTorOnion 2021 channel", e); + throw new Error("Could not uninstall the SecureDropTorOnion update channel"); + } + await this.uninstallTorOnionUpdateChannel(retries - 1); + return; + } + } + + _init() { + if (!this._extensionMessaging) { + this._extensionMessaging = new ExtensionMessaging(); + } + + // update all of the existing https-everywhere channels + setTimeout(async () => { + await this.uninstallTorOnionUpdateChannel(); + + let pinnedChannels = await this._sendMessage("get_pinned_update_channels"); + for(let channel of pinnedChannels.update_channels) { + this._sendMessage("update_update_channel", channel); + } + + let storedChannels = await this._sendMessage("get_stored_update_channels"); + for(let channel of storedChannels.update_channels) { + this._sendMessage("update_update_channel", channel); + } + + this._extensionMessaging.unload(); + this._extensionMessaging = null; + }, 0); + } +} diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 2661ad7cb9f3d..8156853220244 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -1 +1,7 @@ JAR_MANIFESTS += ["jar.mn"] + +EXTRA_JS_MODULES += [ + "ExtensionMessaging.jsm", + "HttpsEverywhereControl.jsm", + "OnionAliasStore.jsm", +]
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 22c25b9bad7f63e53ee1ef95bb8ee8f6095a3364 Author: Alex Catarineu acat@torproject.org AuthorDate: Thu Mar 5 22:16:39 2020 +0100
Bug 21952: Implement Onion-Location
Whenever a valid Onion-Location HTTP header (or corresponding HTML <meta> http-equiv attribute) is found in a document load, we either redirect to it (if the user opted-in via preference) or notify the presence of an onionsite alternative with a badge in the urlbar. --- browser/base/content/browser.js | 12 ++ browser/base/content/navigator-toolbox.inc.xhtml | 3 + browser/components/BrowserGlue.jsm | 13 ++ .../onionservices/OnionLocationChild.jsm | 48 ++++++ .../onionservices/OnionLocationParent.jsm | 168 +++++++++++++++++++++ .../content/onionlocation-notification-icons.css | 5 + .../onionservices/content/onionlocation-urlbar.css | 60 ++++++++ .../content/onionlocation-urlbar.inc.xhtml | 10 ++ .../onionservices/content/onionlocation.svg | 3 + .../content/onionlocationPreferences.inc.xhtml | 11 ++ .../content/onionlocationPreferences.js | 31 ++++ browser/components/onionservices/jar.mn | 2 + browser/components/onionservices/moz.build | 2 + browser/components/preferences/privacy.inc.xhtml | 2 + browser/components/preferences/privacy.js | 17 +++ browser/themes/shared/notification-icons.inc.css | 2 + browser/themes/shared/urlbar-searchbar.inc.css | 1 + dom/base/Document.cpp | 34 ++++- dom/base/Document.h | 2 + dom/webidl/Document.webidl | 8 + modules/libpref/init/StaticPrefList.yaml | 5 + xpcom/ds/StaticAtoms.py | 1 + 22 files changed, 439 insertions(+), 1 deletion(-)
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index f6621a9a74014..faf6433ccacf8 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -48,6 +48,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { NetUtil: "resource://gre/modules/NetUtil.jsm", NewTabUtils: "resource://gre/modules/NewTabUtils.jsm", OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm", + OnionLocationParent: "resource:///modules/OnionLocationParent.jsm", PageActions: "resource:///modules/PageActions.jsm", PageThumbs: "resource://gre/modules/PageThumbs.jsm", PanelMultiView: "resource:///modules/PanelMultiView.jsm", @@ -5301,6 +5302,7 @@ var XULBrowserWindow = { CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser); + OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);
if (!gMultiProcessBrowser) { // Bug 1108553 - Cannot rotate images with e10s @@ -5790,6 +5792,16 @@ var CombinedStopReload = {
var TabsProgressListener = { onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + // Clear OnionLocation UI + if ( + aStateFlags & Ci.nsIWebProgressListener.STATE_START && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aRequest && + aWebProgress.isTopLevel + ) { + OnionLocationParent.onStateChange(aBrowser); + } + // Collect telemetry data about tab load times. if ( aWebProgress.isTopLevel && diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 810a77e57766d..b881492864ae9 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -359,6 +359,9 @@ onclick="FullZoom.reset(); FullZoom.resetScalingZoom();" tooltip="dynamic-shortcut-tooltip" hidden="true"/> + +#include ../../components/onionservices/content/onionlocation-urlbar.inc.xhtml + <hbox id="pageActionButton" class="urlbar-page-action urlbar-icon-wrapper" role="button" diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 16e067c363b2f..08fed785874fd 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -569,6 +569,19 @@ let JSWINDOWACTORS = { allFrames: true, },
+ OnionLocation: { + parent: { + moduleURI: "resource:///modules/OnionLocationParent.jsm", + }, + child: { + moduleURI: "resource:///modules/OnionLocationChild.jsm", + events: { + pageshow: { mozSystemGroup: true }, + }, + }, + messageManagerGroups: ["browsers"], + }, + PageInfo: { child: { moduleURI: "resource:///actors/PageInfoChild.jsm", diff --git a/browser/components/onionservices/OnionLocationChild.jsm b/browser/components/onionservices/OnionLocationChild.jsm new file mode 100644 index 0000000000000..23e1823f5a09a --- /dev/null +++ b/browser/components/onionservices/OnionLocationChild.jsm @@ -0,0 +1,48 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["OnionLocationChild"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +class OnionLocationChild extends JSWindowActorChild { + handleEvent(event) { + this.onPageShow(event); + } + + onPageShow(event) { + if (event.target != this.document) { + return; + } + const onionLocationURI = this.document.onionLocationURI; + if (onionLocationURI) { + this.sendAsyncMessage("OnionLocation:Set"); + } + } + + receiveMessage(aMessage) { + if (aMessage.name == "OnionLocation:Refresh") { + const doc = this.document; + const docShell = this.docShell; + let onionLocationURI = doc.onionLocationURI; + const refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI); + if (onionLocationURI && refreshURI) { + const docUrl = new URL(doc.URL); + let onionUrl = new URL(onionLocationURI.asciiSpec); + // Keep consistent with Location + if (!onionUrl.hash && docUrl.hash) { + onionUrl.hash = docUrl.hash; + onionLocationURI = Services.io.newURI(onionUrl.toString()); + } + refreshURI.refreshURI( + onionLocationURI, + doc.nodePrincipal, + 0, + false, + true + ); + } + } + } +} diff --git a/browser/components/onionservices/OnionLocationParent.jsm b/browser/components/onionservices/OnionLocationParent.jsm new file mode 100644 index 0000000000000..f6250e5548625 --- /dev/null +++ b/browser/components/onionservices/OnionLocationParent.jsm @@ -0,0 +1,168 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["OnionLocationParent"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +// Prefs +const NOTIFICATION_PREF = "privacy.prioritizeonions.showNotification"; +const PRIORITIZE_ONIONS_PREF = "privacy.prioritizeonions.enabled"; + +// Element IDs +const ONIONLOCATION_BOX_ID = "onion-location-box"; +const ONIONLOCATION_BUTTON_ID = "onion-location-button"; +const ONIONLOCATION_LABEL_ID = "onion-label"; + +// Notification IDs +const NOTIFICATION_ID = "onion-location"; +const NOTIFICATION_ANCHOR_ID = "onionlocation"; + +// Strings +const STRING_ONION_AVAILABLE = TorStrings.onionLocation.onionAvailable; +const NOTIFICATION_CANCEL_LABEL = TorStrings.onionLocation.notNow; +const NOTIFICATION_CANCEL_ACCESSKEY = TorStrings.onionLocation.notNowAccessKey; +const NOTIFICATION_OK_LABEL = TorStrings.onionLocation.alwaysPrioritize; +const NOTIFICATION_OK_ACCESSKEY = + TorStrings.onionLocation.alwaysPrioritizeAccessKey; +const NOTIFICATION_TITLE = TorStrings.onionLocation.tryThis; +const NOTIFICATION_DESCRIPTION = TorStrings.onionLocation.description; +const NOTIFICATION_LEARN_MORE_URL = TorStrings.onionLocation.learnMoreURL; + +class OnionLocationParent extends JSWindowActorParent { + // Listeners are added in BrowserGlue.jsm + receiveMessage(aMsg) { + switch (aMsg.name) { + case "OnionLocation:Set": + let browser = this.browsingContext.embedderElement; + OnionLocationParent.setOnionLocation(browser); + break; + } + } + + static buttonClick(event) { + if (event.button !== 0) { + return; + } + const win = event.target.ownerGlobal; + if (win.gBrowser) { + const browser = win.gBrowser.selectedBrowser; + OnionLocationParent.redirect(browser); + } + } + + static redirect(browser) { + let windowGlobal = browser.browsingContext.currentWindowGlobal; + let actor = windowGlobal.getActor("OnionLocation"); + if (actor) { + actor.sendAsyncMessage("OnionLocation:Refresh", {}); + OnionLocationParent.setDisabled(browser); + } + } + + static onStateChange(browser) { + delete browser._onionLocation; + OnionLocationParent.hideNotification(browser); + } + + static setOnionLocation(browser) { + browser._onionLocation = true; + let tabBrowser = browser.getTabBrowser(); + if (tabBrowser && browser === tabBrowser.selectedBrowser) { + OnionLocationParent.updateOnionLocationBadge(browser); + } + } + + static hideNotification(browser) { + const win = browser.ownerGlobal; + if (browser._onionLocationPrompt) { + win.PopupNotifications.remove(browser._onionLocationPrompt); + } + } + + static showNotification(browser) { + const mustShow = Services.prefs.getBoolPref(NOTIFICATION_PREF, true); + if (!mustShow) { + return; + } + + const win = browser.ownerGlobal; + Services.prefs.setBoolPref(NOTIFICATION_PREF, false); + + const mainAction = { + label: NOTIFICATION_OK_LABEL, + accessKey: NOTIFICATION_OK_ACCESSKEY, + callback() { + Services.prefs.setBoolPref(PRIORITIZE_ONIONS_PREF, true); + OnionLocationParent.redirect(browser); + win.openPreferences("privacy-onionservices"); + }, + }; + + const cancelAction = { + label: NOTIFICATION_CANCEL_LABEL, + accessKey: NOTIFICATION_CANCEL_ACCESSKEY, + callback: () => {}, + }; + + const options = { + autofocus: true, + persistent: true, + removeOnDismissal: false, + eventCallback(aTopic) { + if (aTopic === "removed") { + delete browser._onionLocationPrompt; + delete browser.onionpopupnotificationanchor; + } + }, + learnMoreURL: NOTIFICATION_LEARN_MORE_URL, + displayURI: { + hostPort: NOTIFICATION_TITLE, // This is hacky, but allows us to have a title without extra markup/css. + }, + hideClose: true, + popupIconClass: "onionlocation-notification-icon", + }; + + // A hacky way of setting the popup anchor outside the usual url bar icon box + // onionlocationpopupnotificationanchor comes from `${ANCHOR_ID}popupnotificationanchor` + // From https://searchfox.org/mozilla-esr68/rev/080f9ed47742644d2ff84f7aa0b10aea5c44... + browser.onionlocationpopupnotificationanchor = win.document.getElementById( + ONIONLOCATION_BUTTON_ID + ); + + browser._onionLocationPrompt = win.PopupNotifications.show( + browser, + NOTIFICATION_ID, + NOTIFICATION_DESCRIPTION, + NOTIFICATION_ANCHOR_ID, + mainAction, + [cancelAction], + options + ); + } + + static setEnabled(browser) { + const win = browser.ownerGlobal; + const label = win.document.getElementById(ONIONLOCATION_LABEL_ID); + label.textContent = STRING_ONION_AVAILABLE; + const elem = win.document.getElementById(ONIONLOCATION_BOX_ID); + elem.removeAttribute("hidden"); + } + + static setDisabled(browser) { + const win = browser.ownerGlobal; + const elem = win.document.getElementById(ONIONLOCATION_BOX_ID); + elem.setAttribute("hidden", true); + } + + static updateOnionLocationBadge(browser) { + if (browser._onionLocation) { + OnionLocationParent.setEnabled(browser); + OnionLocationParent.showNotification(browser); + } else { + OnionLocationParent.setDisabled(browser); + } + } +} diff --git a/browser/components/onionservices/content/onionlocation-notification-icons.css b/browser/components/onionservices/content/onionlocation-notification-icons.css new file mode 100644 index 0000000000000..7c8a6d892c6ff --- /dev/null +++ b/browser/components/onionservices/content/onionlocation-notification-icons.css @@ -0,0 +1,5 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +.onionlocation-notification-icon { + display: none; +} \ No newline at end of file diff --git a/browser/components/onionservices/content/onionlocation-urlbar.css b/browser/components/onionservices/content/onionlocation-urlbar.css new file mode 100644 index 0000000000000..581175fdbf6be --- /dev/null +++ b/browser/components/onionservices/content/onionlocation-urlbar.css @@ -0,0 +1,60 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +#onion-location-box { + height: 28px; + + background-color: var(--purple-60); + -moz-context-properties: fill; + fill: white; +} + +#onion-location-box:hover { + background-color: var(--purple-70); +} + +#onion-location-box:active { + background-color: var(--purple-80); +} + +@media (prefers-color-scheme: dark) { + #onion-location-box { + background-color: var(--purple-50); + } + + #onion-location-box:hover { + background-color: var(--purple-60); + } + + #onion-location-box:active { + background-color: var(--purple-70); + } +} + +#onion-location-button { + list-style-image: url(chrome://browser/content/onionservices/onionlocation.svg); + padding-inline-start: 0.5em; +} + +label#onion-label { + line-height: 28px; + margin: 0; + padding-block: 0; + padding-inline: 0.5em; + color: white; + font-weight: normal; +} + +/* set appropriate sizes for the non-standard ui densities */ +:root[uidensity=compact] hbox.urlbar-page-action#onion-location-box { + height: 24px; +} +:root[uidensity=compact] label#onion-label { + line-height: 24px; +} + +:root[uidensity=touch] hbox.urlbar-page-action#onion-location-box { + height: 30px; +} +:root[uidensity=touch] label#onion-label { + line-height: 30px; +} diff --git a/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml new file mode 100644 index 0000000000000..b612a4236f3cf --- /dev/null +++ b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml @@ -0,0 +1,10 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<hbox id="onion-location-box" + class="urlbar-icon-wrapper urlbar-page-action" + role="button" + hidden="true" + onclick="OnionLocationParent.buttonClick(event);"> + <image id="onion-location-button" role="presentation"/> + <hbox id="onion-label-container"><label id="onion-label"/></hbox> +</hbox> diff --git a/browser/components/onionservices/content/onionlocation.svg b/browser/components/onionservices/content/onionlocation.svg new file mode 100644 index 0000000000000..37f40ac1812f8 --- /dev/null +++ b/browser/components/onionservices/content/onionlocation.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <path fill="context-fill" fill-opacity="context-fill-opacity" d="m8.016411 14.54499v-0.969784c3.071908-0.0089 5.559239-2.501304 5.559239-5.575429 0-3.073903-2.487331-5.566336-5.559239-5.575206v-0.9697843c3.607473 0.00909 6.528802 2.935521 6.528802 6.544991 0 3.609691-2.921329 6.536342-6.528802 6.545213zm0-3.394356c1.732661-0.0091 3.135111-1.415756 3.135111-3.150857 0-1.734878-1.402451-3.141542-3.135111-3.150634v-0.9695626c2.268448 0.00887 4.104895 1.849753 4.104895 4.120197 0 2.270666- [...] +</svg> \ No newline at end of file diff --git a/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml new file mode 100644 index 0000000000000..c285f403f99bc --- /dev/null +++ b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml @@ -0,0 +1,11 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<groupbox id="onionServicesGroup" data-category="panePrivacy" data-subcategory="onionservices" hidden="true"> + <label><html:h2 id="onionServicesTitle"></html:h2></label> + <label><label class="tail-with-learn-more" id="prioritizeOnionsDesc"></label><label + class="learnMore" is="text-link" id="onionServicesLearnMore"></label></label> + <radiogroup id="prioritizeOnionsRadioGroup" aria-labelledby="prioritizeOnionsDesc" preference="privacy.prioritizeonions.enabled"> + <radio id="onionServicesRadioAlways" value="true"/> + <radio id="onionServicesRadioAsk" value="false"/> + </radiogroup> +</groupbox> diff --git a/browser/components/onionservices/content/onionlocationPreferences.js b/browser/components/onionservices/content/onionlocationPreferences.js new file mode 100644 index 0000000000000..aa569b54721cd --- /dev/null +++ b/browser/components/onionservices/content/onionlocationPreferences.js @@ -0,0 +1,31 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +const OnionLocationPreferences = { + init() { + document.getElementById("onionServicesTitle").textContent = + TorStrings.onionLocation.onionServicesTitle; + document.getElementById("prioritizeOnionsDesc").textContent = + TorStrings.onionLocation.prioritizeOnionsDescription; + const learnMore = document.getElementById("onionServicesLearnMore"); + learnMore.textContent = TorStrings.onionLocation.learnMore; + learnMore.href = TorStrings.onionLocation.learnMoreURL; + document.getElementById("onionServicesRadioAlways").label = + TorStrings.onionLocation.always; + document.getElementById("onionServicesRadioAsk").label = + TorStrings.onionLocation.askEverytime; + }, +}; + +Object.defineProperty(this, "OnionLocationPreferences", { + value: OnionLocationPreferences, + enumerable: true, + writable: false, +}); diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn index 9d6ce88d18416..f45b16dc5d293 100644 --- a/browser/components/onionservices/jar.mn +++ b/browser/components/onionservices/jar.mn @@ -7,3 +7,5 @@ browser.jar: content/browser/onionservices/onionservices.css (content/onionservices.css) content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js) content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml) + content/browser/onionservices/onionlocationPreferences.js (content/onionlocationPreferences.js) + content/browser/onionservices/onionlocation.svg (content/onionlocation.svg) diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 8156853220244..8027233d65a68 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -4,4 +4,6 @@ EXTRA_JS_MODULES += [ "ExtensionMessaging.jsm", "HttpsEverywhereControl.jsm", "OnionAliasStore.jsm", + "OnionLocationChild.jsm", + "OnionLocationParent.jsm", ] diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index 18d01fee6b339..66d7ef724f831 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -14,6 +14,8 @@ <html:h1 data-l10n-id="privacy-header"/> </hbox>
+#include ../onionservices/content/onionlocationPreferences.inc.xhtml + <!-- Tracking / Content Blocking --> <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" aria-describedby="contentBlockingDescription"> <label id="contentBlockingHeader"><html:h2 data-l10n-id="content-blocking-enhanced-tracking-protection"/></label> diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js index 932d4291e4862..1985b5489fc1c 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -93,6 +93,12 @@ XPCOMUtils.defineLazyScriptGetter( "chrome://browser/content/securitylevel/securityLevel.js" );
+XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionLocationPreferences"], + "chrome://browser/content/onionservices/onionlocationPreferences.js" +); + XPCOMUtils.defineLazyServiceGetter( this, "listManager", @@ -169,6 +175,9 @@ Preferences.addAll([ // Do not track { id: "privacy.donottrackheader.enabled", type: "bool" },
+ // Onion Location + { id: "privacy.prioritizeonions.enabled", type: "bool" }, + // Media { id: "media.autoplay.default", type: "int" },
@@ -333,6 +342,13 @@ var gPrivacyPane = { window.addEventListener("unload", unload); },
+ /** + * Show the OnionLocation preferences UI + */ + _initOnionLocation() { + OnionLocationPreferences.init(); + }, + /** * Whether the prompt to restart Firefox should appear when changing the autostart pref. */ @@ -530,6 +546,7 @@ var gPrivacyPane = { this._initTrackingProtectionExtensionControl(); OnionServicesAuthPreferences.init(); this._initSecurityLevel(); + this._initOnionLocation();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css index 67dd640baf162..83248f71c60d7 100644 --- a/browser/themes/shared/notification-icons.inc.css +++ b/browser/themes/shared/notification-icons.inc.css @@ -449,3 +449,5 @@ -moz-context-properties: fill; fill: var(--panel-banner-item-warning-icon-bgcolor); } + +%include ../../components/onionservices/content/onionlocation-notification-icons.css diff --git a/browser/themes/shared/urlbar-searchbar.inc.css b/browser/themes/shared/urlbar-searchbar.inc.css index f91278ce5ed3b..d7dc7df17f19d 100644 --- a/browser/themes/shared/urlbar-searchbar.inc.css +++ b/browser/themes/shared/urlbar-searchbar.inc.css @@ -746,5 +746,6 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after { opacity: 0.69; }
+%include ../../components/onionservices/content/onionlocation-urlbar.css %include ../../components/torconnect/content/torconnect-urlbar.css
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 5df0e24bec398..7bce87a3cbdb8 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -2779,6 +2779,7 @@ void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, // mDocumentURI. mDocumentBaseURI = nullptr; mChromeXHRDocBaseURI = nullptr; + mOnionLocationURI = nullptr;
// Check if the current document is the top-level DevTools document. // For inner DevTools frames, mIsDevToolsDocument will be set when @@ -6511,6 +6512,22 @@ void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const { } }
+static bool IsValidOnionLocation(nsIURI* aDocumentURI, + nsIURI* aOnionLocationURI) { + bool isHttpish; + nsAutoCString host; + return aDocumentURI && aOnionLocationURI && + NS_SUCCEEDED(aDocumentURI->SchemeIs("https", &isHttpish)) && + isHttpish && NS_SUCCEEDED(aDocumentURI->GetAsciiHost(host)) && + !StringEndsWith(host, ".onion"_ns) && + ((NS_SUCCEEDED(aOnionLocationURI->SchemeIs("http", &isHttpish)) && + isHttpish) || + (NS_SUCCEEDED(aOnionLocationURI->SchemeIs("https", &isHttpish)) && + isHttpish)) && + NS_SUCCEEDED(aOnionLocationURI->GetAsciiHost(host)) && + StringEndsWith(host, ".onion"_ns); +} + void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { if (!aHeaderField) { NS_ERROR("null headerField"); @@ -6585,6 +6602,21 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { aHeaderField == nsGkAtoms::handheldFriendly) { mViewportType = Unknown; } + + if (aHeaderField == nsGkAtoms::headerOnionLocation && !aData.IsEmpty()) { + nsCOMPtr<nsIURI> onionURI; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(onionURI), aData)) && + IsValidOnionLocation(Document::GetDocumentURI(), onionURI)) { + if (StaticPrefs::privacy_prioritizeonions_enabled()) { + nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer); + if (refresher) { + refresher->RefreshURI(onionURI, NodePrincipal(), 0, false, true); + } + } else { + mOnionLocationURI = onionURI; + } + } + } }
void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource, @@ -10722,7 +10754,7 @@ void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) { static const char* const headers[] = { "default-style", "content-style-type", "content-language", "content-disposition", "refresh", "x-dns-prefetch-control", - "x-frame-options", + "x-frame-options", "onion-location", // add more http headers if you need // XXXbz don't add content-location support without reading bug // 238654 and its dependencies/dups first. diff --git a/dom/base/Document.h b/dom/base/Document.h index 69e59d09b9245..7ef73651d47fa 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3371,6 +3371,7 @@ class Document : public nsINode, void ReleaseCapture() const; void MozSetImageElement(const nsAString& aImageElementId, Element* aElement); nsIURI* GetDocumentURIObject() const; + nsIURI* GetOnionLocationURI() const { return mOnionLocationURI; } // Not const because all the fullscreen goop is not const const char* GetFullscreenError(CallerType); bool FullscreenEnabled(CallerType aCallerType) { @@ -4354,6 +4355,7 @@ class Document : public nsINode, nsCOMPtr<nsIURI> mChromeXHRDocURI; nsCOMPtr<nsIURI> mDocumentBaseURI; nsCOMPtr<nsIURI> mChromeXHRDocBaseURI; + nsCOMPtr<nsIURI> mOnionLocationURI;
// The base domain of the document for third-party checks. nsCString mBaseDomain; diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index a139ace11d4a2..d934cf8da0455 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -711,3 +711,11 @@ partial interface Document { [ChromeOnly] void setNotifyFormOrPasswordRemoved(boolean aShouldNotify); }; + +/** + * Extension to allows chrome JS to know whether the document has a valid + * Onion-Location that we could redirect to. + */ +partial interface Document { + [ChromeOnly] readonly attribute URI? onionLocationURI; +}; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 058df55a189d4..3f7b2b992f2dc 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -10472,6 +10472,11 @@ value: "" mirror: never
+- name: privacy.prioritizeonions.enabled + type: RelaxedAtomicBool + value: false + mirror: always + #--------------------------------------------------------------------------- # Prefs starting with "prompts." #--------------------------------------------------------------------------- diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py index 2f5be143517ba..f620f57a12135 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -821,6 +821,7 @@ STATIC_ATOMS = [ Atom("oninputsourceschange", "oninputsourceschange"), Atom("oninstall", "oninstall"), Atom("oninvalid", "oninvalid"), + Atom("headerOnionLocation", "onion-location"), Atom("onkeydown", "onkeydown"), Atom("onkeypress", "onkeypress"), Atom("onkeyup", "onkeyup"),
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 4c6c26f9a02a80a479aac25d018a004574c4eeaa Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon Feb 21 15:39:11 2022 +0100
Bug 40458: Implement .tor.onion aliases
We have enabled HTTPS-Only mode, therefore we do not need HTTPS-Everywhere anymore. However, we want to keep supporting .tor.onion aliases (especially for securedrop). Therefore, in this patch we implemented the parsing of HTTPS-Everywhere rulesets, and the redirect of .tor.onion domains. Actually, Tor Browser believes they are actual domains. We change them on the fly on the SOCKS proxy requests to resolve the domain, and on the code that verifies HTTPS certificates. --- browser/base/content/browser-siteIdentity.js | 4 +- browser/components/BrowserGlue.jsm | 42 ++ browser/components/about/AboutRedirector.cpp | 4 + browser/components/about/components.conf | 1 + browser/components/moz.build | 1 + .../components/onionservices/OnionAliasStore.jsm | 520 ++++++++++++++++++++ browser/components/rulesets/RulesetsChild.jsm | 11 + browser/components/rulesets/RulesetsParent.jsm | 79 +++ .../components/rulesets/content/aboutRulesets.css | 319 +++++++++++++ .../components/rulesets/content/aboutRulesets.html | 110 +++++ .../components/rulesets/content/aboutRulesets.js | 531 +++++++++++++++++++++ browser/components/rulesets/content/securedrop.svg | 173 +++++++ browser/components/rulesets/jar.mn | 5 + browser/components/rulesets/moz.build | 6 + netwerk/build/components.conf | 11 + netwerk/build/nsNetCID.h | 10 + netwerk/dns/IOnionAliasService.idl | 34 ++ netwerk/dns/OnionAliasService.cpp | 94 ++++ netwerk/dns/OnionAliasService.h | 36 ++ netwerk/dns/effective_tld_names.dat | 2 + netwerk/dns/moz.build | 4 + netwerk/socket/nsSOCKSIOLayer.cpp | 24 +- security/manager/ssl/SSLServerCertVerification.cpp | 9 + security/manager/ssl/SSLServerCertVerification.h | 4 +- toolkit/modules/RemotePageAccessManager.jsm | 14 + 25 files changed, 2040 insertions(+), 8 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 6682ae8b096fe..5e70d458094cf 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -58,8 +58,8 @@ var gIdentityHandler = { * the browser UI. */ _secureInternalPages: (AppConstants.TOR_BROWSER_UPDATE ? - /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : - /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i), + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i),
/** * Whether the established HTTPS connection is considered "broken". diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 08fed785874fd..882a1724bb353 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -85,6 +85,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { TabUnloader: "resource:///modules/TabUnloader.jsm", TelemetryUtils: "resource://gre/modules/TelemetryUtils.jsm", TRRRacer: "resource:///modules/TRRPerformance.jsm", + OnionAliasStore: "resource:///modules/OnionAliasStore.jsm", UIState: "resource://services-sync/UIState.jsm", UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm", UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm", @@ -669,6 +670,19 @@ let JSWINDOWACTORS = { enablePreference: "accessibility.blockautorefresh", },
+ Rulesets: { + parent: { + moduleURI: "resource:///modules/RulesetsParent.jsm", + }, + child: { + moduleURI: "resource:///modules/RulesetsChild.jsm", + events: { + DOMWindowCreated: {}, + }, + }, + matches: ["about:rulesets*"], + }, + SearchSERPTelemetry: { parent: { moduleURI: "resource:///actors/SearchSERPTelemetryParent.jsm", @@ -2028,6 +2042,7 @@ BrowserGlue.prototype = { Normandy.uninit(); RFPHelper.uninit(); ASRouterNewTabHook.destroy(); + OnionAliasStore.uninit(); },
// Set up a listener to enable/disable the screenshots extension @@ -2532,6 +2547,33 @@ BrowserGlue.prototype = { }, },
+ { + task: () => { + const { TorConnect, TorConnectTopics } = ChromeUtils.import( + "resource:///modules/TorConnect.jsm" + ); + if (!TorConnect.shouldShowTorConnect) { + // we will take this path when the user is using the legacy tor launcher or + // when Tor Browser didn't launch its own tor. + OnionAliasStore.init(); + } else { + // this path is taken when using about:torconnect, we wait to init + // after we are bootstrapped and connected to tor + const topic = TorConnectTopics.BootstrapComplete; + let bootstrapObserver = { + observe(aSubject, aTopic, aData) { + if (aTopic === topic) { + OnionAliasStore.init(); + // we only need to init once, so remove ourselves as an obvserver + Services.obs.removeObserver(this, topic); + } + } + }; + Services.obs.addObserver(bootstrapObserver, topic); + } + }, + }, + { task: () => { Blocklist.loadBlocklistAsync(); diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index fd828a630c92a..57c4d40c8ef56 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -68,6 +68,10 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"rulesets", "chrome://browser/content/rulesets/aboutRulesets.html", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, {"tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 0916bb75e1d57..df7c05b3193d5 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -24,6 +24,7 @@ pages = [ 'restartrequired', 'rights', 'robots', + 'rulesets', 'sessionrestore', 'tabcrashed', 'torconnect', diff --git a/browser/components/moz.build b/browser/components/moz.build index c304973749122..0eefdd0a06349 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -48,6 +48,7 @@ DIRS += [ "prompts", "protocolhandler", "resistfingerprinting", + "rulesets", "search", "securitylevel", "sessionstore", diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm new file mode 100644 index 0000000000000..cc48288e356ee --- /dev/null +++ b/browser/components/onionservices/OnionAliasStore.jsm @@ -0,0 +1,520 @@ +// Copyright (c) 2022, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["OnionAliasStore", "OnionAliasStoreTopics"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +ChromeUtils.defineModuleGetter( + this, + "JSONFile", + "resource://gre/modules/JSONFile.jsm" +); + +const { HttpsEverywhereControl } = ChromeUtils.import( + "resource:///modules/HttpsEverywhereControl.jsm" +); + +Cu.importGlobalProperties(["crypto", "fetch"]); + +/* OnionAliasStore observer topics */ +const OnionAliasStoreTopics = Object.freeze({ + ChannelsChanged: "onionaliasstore:channels-changed", +}); + +const SECURE_DROP = { + name: "SecureDropTorOnion2021", + pathPrefix: "https://securedrop.org/https-everywhere-2021/", + jwk: { + kty: "RSA", + e: "AQAB", + n: + "vsC7BNafkRe8Uh1DUgCkv6RbPQMdJgAKKnWdSqQd7tQzU1mXfmo_k1Py_2MYMZXOWmqSZ9iwIYkykZYywJ2VyMGve4byj1sLn6YQoOkG8g5Z3V4y0S2RpEfmYumNjTzfq8nxtLnwjaYd4sCUd5wa0SzeLrpRQuXo2bF3QuUF2xcbLJloxX1MmlsMMCdBc-qGNonLJ7bpn_JuyXlDWy1Fkeyw1qgjiOdiRIbMC1x302zgzX6dSrBrNB8Cpsh-vCE0ZjUo8M9caEv06F6QbYmdGJHM0ZZY34OHMSNdf-_qUKIV_SuxuSuFE99tkAeWnbWpyI1V-xhVo1sc7NzChP8ci2TdPvI3_0JyAuCvL6zIFqJUJkZibEUghhg6F09-oNJKpy7rhUJq7zZyLXJsvuXnn0gnIxfjRvMcDfZAKUVMZKRdw7fwWzwQril4Ib0MQOVda9vb_4JMk7Gup-TUI4sfuS4NKwsnKoODIO-2U [...] + }, + scope: /^https?://[a-z0-9-]+(?:.[a-z0-9-]+)*.securedrop.tor.onion//, + enabled: true, + mappings: [], + currentTimestamp: 0, +}; + +// Logger adapted from CustomizableUI.jsm +const kPrefOnionAliasDebug = "browser.onionalias.debug"; +XPCOMUtils.defineLazyPreferenceGetter( + this, + "gDebuggingEnabled", + kPrefOnionAliasDebug, + false, + (pref, oldVal, newVal) => { + if (typeof log != "undefined") { + log.maxLogLevel = newVal ? "all" : "log"; + } + } +); +XPCOMUtils.defineLazyGetter(this, "log", () => { + let scope = {}; + ChromeUtils.import("resource://gre/modules/Console.jsm", scope); + let consoleOptions = { + maxLogLevel: gDebuggingEnabled ? "all" : "log", + prefix: "OnionAlias", + }; + return new scope.ConsoleAPI(consoleOptions); +}); + +// Inspired by aboutMemory.js and PingCentre.jsm +function gunzip(buffer) { + return new Promise((resolve, reject) => { + const listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance( + Ci.nsIStreamLoader + ); + listener.init({ + onStreamComplete(loader, context, status, length, result) { + resolve(String.fromCharCode(...result)); + }, + }); + const scs = Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); + const converter = scs.asyncConvertData( + "gzip", + "uncompressed", + listener, + null + ); + const stream = Cc[ + "@mozilla.org/io/arraybuffer-input-stream;1" + ].createInstance(Ci.nsIArrayBufferInputStream); + stream.setData(buffer, 0, buffer.byteLength); + converter.onStartRequest(null, null); + converter.onDataAvailable(null, stream, 0, buffer.byteLength); + converter.onStopRequest(null, null, null); + }); +} + +class Channel { + static get SIGN_ALGORITHM() { + return { + name: "RSA-PSS", + saltLength: 32, + hash: { name: "SHA-256" }, + }; + } + + constructor(name, pathPrefix, jwk, scope, enabled) { + this.name = name; + this.pathPrefix = pathPrefix; + this.jwk = jwk; + this.scope = scope; + this._enabled = enabled; + + this.mappings = []; + this.currentTimestamp = 0; + this.latestTimestamp = 0; + } + + async updateLatestTimestamp() { + const timestampUrl = this.pathPrefix + "/latest-rulesets-timestamp"; + log.debug(`Updating ${this.name} timestamp from ${timestampUrl}`); + const response = await fetch(timestampUrl); + if (!response.ok) { + throw Error(`Could not fetch timestamp for ${this.name}`, { + cause: response.status, + }); + } + const timestampStr = await response.text(); + const timestamp = parseInt(timestampStr); + // Avoid hijacking, sanitize the timestamp + if (isNaN(timestamp)) { + throw Error("Latest timestamp is not a number"); + } + log.debug(`Updated ${this.name} timestamp: ${timestamp}`); + this.latestTimestamp = timestamp; + } + + async makeKey() { + return crypto.subtle.importKey( + "jwk", + this.jwk, + Channel.SIGN_ALGORITHM, + false, + ["verify"] + ); + } + + async downloadVerifiedRules() { + log.debug(`Downloading and verifying ruleset for ${this.name}`); + + const key = await this.makeKey(); + const signatureUrl = + this.pathPrefix + `/rulesets-signature.${this.latestTimestamp}.sha256`; + const signatureResponse = await fetch(signatureUrl); + if (!signatureResponse.ok) { + throw Error("Could not fetch the rules signature"); + } + const signature = await signatureResponse.arrayBuffer(); + + const rulesUrl = + this.pathPrefix + `/default.rulesets.${this.latestTimestamp}.gz`; + const rulesResponse = await fetch(rulesUrl); + if (!rulesResponse.ok) { + throw Error("Could not fetch rules"); + } + const rulesGz = await rulesResponse.arrayBuffer(); + + if ( + !(await crypto.subtle.verify( + Channel.SIGN_ALGORITHM, + key, + signature, + rulesGz + )) + ) { + throw Error("Could not verify rules signature"); + } + log.debug( + `Downloaded and verified rules for ${this.name}, now uncompressing` + ); + this._makeMappings(JSON.parse(await gunzip(rulesGz))); + } + + _makeMappings(rules) { + const toTest = /http[s]?://[a-zA-Z0-9.]{56}.onion/; + const mappings = []; + rules.rulesets.forEach(rule => { + if (rule.rule.length != 1) { + log.warn(`Unsupported rule lenght: ${rule.rule.length}`); + return; + } + if (!toTest.test(rule.rule[0].to)) { + log.warn( + `Ignoring rule, because of a malformed to: ${rule.rule[0].to}` + ); + return; + } + let toHostname; + try { + const toUrl = new URL(rule.rule[0].to); + toHostname = toUrl.hostname; + } catch (err) { + log.error( + "Cannot detect the hostname from the to rule", + rule.rule[0].to, + err + ); + } + let fromRe; + try { + fromRe = new RegExp(rule.rule[0].from); + } catch (err) { + log.error("Malformed from field", rule.rule[0].from, err); + return; + } + for (const target of rule.target) { + if ( + target.endsWith(".tor.onion") && + this.scope.test(`http://$%7Btarget%7D/%60) && + fromRe.test(`http://$%7Btarget%7D/%60) + ) { + mappings.push([target, toHostname]); + } else { + log.warn("Ignoring malformed rule", rule); + } + } + }); + this.mappings = mappings; + this.currentTimestamp = rules.timestamp; + log.debug(`Updated mappings for ${this.name}`, mappings); + } + + async updateMappings(force) { + force = force === undefined ? false : !!force; + if (!this._enabled && !force) { + return; + } + await this.updateLatestTimestamp(); + if (this.latestTimestamp <= this.currentTimestamp && !force) { + log.debug( + `Rules for ${this.name} are already up to date, skipping update` + ); + return; + } + await this.downloadVerifiedRules(); + } + + get enabled() { + return this._enabled; + } + set enabled(enabled) { + this._enabled = enabled; + if (!enabled) { + this.mappings = []; + this.currentTimestamp = 0; + this.latestTimestamp = 0; + } + } + + toJSON() { + let scope = this.scope.toString(); + scope = scope.substr(1, scope.length - 2); + return { + name: this.name, + pathPrefix: this.pathPrefix, + jwk: this.jwk, + scope, + enabled: this._enabled, + mappings: this.mappings, + currentTimestamp: this.currentTimestamp, + }; + } + + static fromJSON(obj) { + let channel = new Channel( + obj.name, + obj.pathPrefix, + obj.jwk, + new RegExp(obj.scope), + obj.enabled + ); + if (obj.enabled) { + channel.mappings = obj.mappings; + channel.currentTimestamp = obj.currentTimestamp; + } + return channel; + } +} + +class _OnionAliasStore { + static get RULESET_CHECK_INTERVAL() { + return 86400 * 1000; // 1 day, like HTTPS-Everywhere + } + + constructor() { + this._channels = new Map(); + this._rulesetTimeout = null; + this._lastCheck = 0; + this._storage = null; + } + + async init() { + await this._loadSettings(); + const dt = Date.now() - this._lastCheck; + let force = false; + for (const ch of this._channels.values()) { + if (ch.enabled && !ch.currentTimestamp) { + // Edited while being offline or some other error happened + force = true; + break; + } + } + if (dt > _OnionAliasStore.RULESET_CHECK_INTERVAL || force) { + log.debug( + `Mappings are stale (${dt}), or force check requested (${force}), checking them immediately` + ); + await this._periodicRulesetCheck(); + } else { + this._scheduleCheck(_OnionAliasStore.RULESET_CHECK_INTERVAL - dt); + } + + new HttpsEverywhereControl(); + } + + uninit() { + this._clear(); + clearTimeout(this._rulesetTimeout); + this._rulesetTimeout = null; + } + + async getChannels() { + if (this._storage === null) { + await this._loadSettings(); + } + return Array.from(this._channels.values(), ch => ch.toJSON()); + } + + async setChannel(chanData) { + const name = chanData.name?.trim(); + if (!name) { + throw Error("Name cannot be empty"); + } + + new URL(chanData.pathPrefix); + const scope = new RegExp(chanData.scope); + const ch = new Channel( + name, + chanData.pathPrefix, + chanData.jwk, + scope, + !!chanData.enabled + ); + // Call makeKey to make it throw if the key is invalid + await ch.makeKey(); + this._channels.set(name, ch); + this._applyMappings(); + this._saveSettings(); + setTimeout(this._notifyChanges.bind(this), 1); + return ch; + } + + enableChannel(name, enabled) { + const channel = this._channels.get(name); + if (channel !== null) { + channel.enabled = enabled; + this._applyMappings(); + this._saveSettings(); + this._notifyChanges(); + if (enabled && !channel.currentTimestamp) { + this.updateChannel(name); + } + } + } + + async updateChannel(name) { + const channel = this._channels.get(name); + if (channel === null) { + throw Error("Channel not found"); + } + await channel.updateMappings(true); + this._saveSettings(); + this._applyMappings(); + setTimeout(this._notifyChanges.bind(this), 1); + return channel; + } + + deleteChannel(name) { + if (this._channels.delete(name)) { + this._saveSettings(); + this._applyMappings(); + this._notifyChanges(); + } + } + + async _loadSettings() { + if (this._storage !== null) { + return; + } + this._channels = new Map(); + this._storage = new JSONFile({ + path: PathUtils.join( + Services.dirsvc.get("ProfD", Ci.nsIFile).path, + "onion-aliases.json" + ), + dataPostProcessor: this._settingsProcessor.bind(this), + }); + await this._storage.load(); + log.debug("Loaded settings", this._storage.data, this._storage.path); + this._applyMappings(); + this._notifyChanges(); + } + + _settingsProcessor(data) { + if ("lastCheck" in data) { + this._lastCheck = data.lastCheck; + } else { + data.lastCheck = 0; + } + if (!("channels" in data) || !Array.isArray(data.channels)) { + data.channels = [SECURE_DROP]; + // Force updating + data.lastCheck = 0; + } + const channels = new Map(); + data.channels = data.channels.filter(ch => { + try { + channels.set(ch.name, Channel.fromJSON(ch)); + } catch (err) { + log.error("Could not load a channel", err, ch); + return false; + } + return true; + }); + this._channels = channels; + return data; + } + + _saveSettings() { + if (this._storage === null) { + throw Error("Settings have not been loaded"); + } + this._storage.data.lastCheck = this._lastCheck; + this._storage.data.channels = Array.from(this._channels.values(), ch => + ch.toJSON() + ); + this._storage.saveSoon(); + } + + _addMapping(shortOnionHost, longOnionHost) { + const service = Cc["@torproject.org/onion-alias-service;1"].getService( + Ci.IOnionAliasService + ); + service.addOnionAlias(shortOnionHost, longOnionHost); + } + + _clear() { + const service = Cc["@torproject.org/onion-alias-service;1"].getService( + Ci.IOnionAliasService + ); + service.clearOnionAliases(); + } + + _applyMappings() { + this._clear(); + for (const ch of this._channels.values()) { + if (!ch.enabled) { + continue; + } + for (const [short, long] of ch.mappings) { + this._addMapping(short, long); + } + } + } + + async _periodicRulesetCheck() { + log.debug("Begin scheduled ruleset update"); + this._lastCheck = Date.now(); + let anyUpdated = false; + for (const ch of this._channels.values()) { + if (!ch.enabled) { + log.debug(`Not updating ${ch.name} because not enabled`); + continue; + } + log.debug(`Updating ${ch.name}`); + try { + await ch.updateMappings(); + anyUpdated = true; + } catch (err) { + log.error(`Could not update mappings for channel ${ch.name}`, err); + } + } + if (anyUpdated) { + this._saveSettings(); + this._applyMappings(); + this._notifyChanges(); + } else { + log.debug("No channel has been updated, avoid saving"); + } + this._scheduleCheck(_OnionAliasStore.RULESET_CHECK_INTERVAL); + } + + _scheduleCheck(dt) { + log.debug(`Scheduling ruleset update in ${dt}`); + this._rulesetTimeout = setTimeout( + this._periodicRulesetCheck.bind(this), + dt + ); + } + + _notifyChanges() { + Services.obs.notifyObservers( + Array.from(this._channels.values(), ch => ch.toJSON()), + OnionAliasStoreTopics.ChannelsChanged + ); + } +} + +const OnionAliasStore = new _OnionAliasStore(); diff --git a/browser/components/rulesets/RulesetsChild.jsm b/browser/components/rulesets/RulesetsChild.jsm new file mode 100644 index 0000000000000..e30de9c7201bd --- /dev/null +++ b/browser/components/rulesets/RulesetsChild.jsm @@ -0,0 +1,11 @@ +// Copyright (c) 2022, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["RulesetsChild"]; + +const { RemotePageChild } = ChromeUtils.import( + "resource://gre/actors/RemotePageChild.jsm" +); + +class RulesetsChild extends RemotePageChild {} diff --git a/browser/components/rulesets/RulesetsParent.jsm b/browser/components/rulesets/RulesetsParent.jsm new file mode 100644 index 0000000000000..6a9553644cb17 --- /dev/null +++ b/browser/components/rulesets/RulesetsParent.jsm @@ -0,0 +1,79 @@ +// Copyright (c) 2022, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["RulesetsParent"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); +const { OnionAliasStore, OnionAliasStoreTopics } = ChromeUtils.import( + "resource:///modules/OnionAliasStore.jsm" +); + +const kShowWarningPref = "torbrowser.rulesets.show_warning"; + +// This class allows about:rulesets to get TorStrings and to load/save the +// preference for skipping the warning +class RulesetsParent extends JSWindowActorParent { + constructor(...args) { + super(...args); + + const self = this; + this.observer = { + observe(aSubject, aTopic, aData) { + const obj = aSubject?.wrappedJSObject; + if (aTopic === OnionAliasStoreTopics.ChannelsChanged && obj) { + self.sendAsyncMessage("rulesets:channels-change", obj); + } + }, + }; + Services.obs.addObserver( + this.observer, + OnionAliasStoreTopics.ChannelsChanged + ); + } + + willDestroy() { + Services.obs.removeObserver( + this.observer, + OnionAliasStoreTopics.ChannelsChanged + ); + } + + async receiveMessage(message) { + switch (message.name) { + // RPMSendAsyncMessage + case "rulesets:delete-channel": + OnionAliasStore.deleteChannel(message.data); + break; + case "rulesets:enable-channel": + OnionAliasStore.enableChannel(message.data.name, message.data.enabled); + break; + case "rulesets:set-show-warning": + Services.prefs.setBoolPref(kShowWarningPref, message.data); + break; + // RPMSendQuery + case "rulesets:get-channels": + return OnionAliasStore.getChannels(); + case "rulesets:get-init-args": + return { + TorStrings, + showWarning: Services.prefs.getBoolPref(kShowWarningPref, true), + }; + case "rulesets:set-channel": + const ch = await OnionAliasStore.setChannel(message.data); + return ch; + case "rulesets:update-channel": + // We need to catch any error in this way, because in case of an + // exception, RPMSendQuery does not return on the other side + try { + const channel = await OnionAliasStore.updateChannel(message.data); + return channel; + } catch (err) { + console.error("Cannot update the channel", err); + return { error: err.toString() }; + } + } + return undefined; + } +} diff --git a/browser/components/rulesets/content/aboutRulesets.css b/browser/components/rulesets/content/aboutRulesets.css new file mode 100644 index 0000000000000..60b699fe8a02a --- /dev/null +++ b/browser/components/rulesets/content/aboutRulesets.css @@ -0,0 +1,319 @@ +/* Copyright (c) 2022, The Tor Project, Inc. */ + +/* General rules */ + +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +body { + font: message-box; + background-color: var(--in-content-page-background); + color: var(--in-content-page-color); + font-size: 15px; + cursor: default; +} + +label { + display: flex; + align-items: center; + padding: 6px 0; +} + +input[type=text] { + margin: 0; + width: 360px; + max-width: 100%; +} + +textarea { + margin: 0; + width: var(--content-width); + max-width: 100%; + box-sizing: border-box; +} + +select, option { + font-weight: 700; +} + +dt { + margin: var(--ruleset-vmargin) 0 0 0; + padding: 0; + color: var(--in-content-deemphasized-text); + font-size: 85%; +} + +dd { + margin: 8px 0 0 0; + padding: 0; + max-width: 600px; + box-sizing: border-box; + line-height: 1.4; +} + +hr { + width: 40px; + margin: 0; + border: none; + border-top: 1px solid var(--in-content-border-color); +} + +.hidden { + display: none !important; +} + +/* Initial warning */ + +#warning-wrapper { + display: none; +} + +.state-warning #warning-wrapper { + display: flex; + align-items: center; + height: 100%; +} + +#warning { + margin-top: -20vh; + padding: 0 160px; + background-image: url("chrome://global/skin/icons/warning.svg"); + background-position: 84px 0; + background-repeat: no-repeat; + background-size: 48px; + fill: #ffbd4f; + -moz-context-properties: fill; +} + +#warning:dir(rtl) { + background-position: right 84px top 0; +} + +#warning-description { + margin: 30px 0 16px 0; +} + +#warning-buttonbar { + margin-top: 30px; + text-align: right; +} + +/* Actual content */ + +:root { + --sidebar-width: 320px; + --content-width: 600px; + --ruleset-vmargin: 40px; +} + +#main-content { + display: flex; + height: 100%; +} + +.state-warning #main-content { + display: none; +} + +section { + display: none; + flex: 1 0 auto; + padding: 40px; +} + +.title { + display: flex; + align-items: center; + width: var(--content-width); + max-width: 100%; + padding-bottom: 16px; + border-bottom: 1px solid var(--in-content-border-color); +} + +.title h1 { + margin: 0; + padding: 0; + padding-inline-start: 35px; + font-size: 20px; + font-weight: 700; + line-height: 30px; + background-image: url("chrome://browser/content/rulesets/securedrop.svg"); + background-position: 0 4px; + background-size: 22px; + background-repeat: no-repeat; +} + +#main-content h1:dir(rtl) { + background-position: right 0 top 4px; +} + +/* Ruleset list */ + +aside { + display: flex; + flex-direction: column; + flex: 0 0 var(--sidebar-width); + box-sizing: border-box; + + border-inline-end: 1px solid var(--in-content-border-color); + background-color: var(--in-content-box-background); +} + +#ruleset-heading { + padding: 16px; + text-align: center; + font-weight: 700; + border-bottom: 1px solid var(--in-content-border-color); +} + +#ruleset-list-container { + flex: 1; +} + +#ruleset-list-empty { + padding: 16px; + text-align: center; +} + +#ruleset-list-empty-description { + font-size: 80%; +} + +#ruleset-list { + margin: 0; + padding: 0; +} + +#ruleset-list li { + display: flex; + align-items: center; + margin: 0; + padding: 10px 18px; + list-style: none; + border-inline-start: 4px solid transparent; + border-bottom: 1px solid var(--in-content-border-color); +} + +#ruleset-list li:last-child { + border-bottom: none; +} + +#ruleset-list .icon { + width: 16px; + height: 16px; + margin-inline-end: 12px; + background-image: url("chrome://browser/content/rulesets/securedrop.svg"); + background-size: 16px; +} + +#ruleset-list .icon.has-favicon { + background: transparent; +} + +#ruleset-list .name { + font-weight: 700; +} + +#ruleset-list .description { + font-size: 85%; + color: var(--in-content-deemphasized-text); +} + +#ruleset-list .selected { + border-inline-start-color: var(--in-content-accent-color); +} + +#ruleset-list .selected.disabled { + border-inline-start-color: var(--in-content-border-color); +} + +#ruleset-list li:not(.selected):hover { + background-color: var(--in-content-button-background-hover); + color: var(--in-content-button-text-color-hover); +} + +#ruleset-list li:not(.selected):hover:active { + background-color: var(--in-content-button-background-active); +} + +#ruleset-list #ruleset-template { + display: none; +} + +/* Ruleset details */ + +.state-details #ruleset-details { + display: block; +} + +#ruleset-jwk-value { + padding: 8px; + border-radius: 2px; + background-color: var(--in-content-box-background); + font-size: 85%; + line-break: anywhere; +} + +#ruleset-edit { + margin-inline-start: auto; + padding-inline-start: 32px; + background-image: url("chrome://global/skin/icons/edit.svg"); + background-repeat: no-repeat; + background-position: 8px; + -moz-context-properties: fill; + fill: currentColor; + min-width: auto; + flex: 0 0 auto; +} + +#ruleset-enable { + margin-top: var(--ruleset-vmargin); +} + +#ruleset-buttonbar { + margin: var(--ruleset-vmargin) 0; +} + +#ruleset-updated { + margin-top: 24px; + color: var(--in-content-deemphasized-text); + font-size: 85%; +} + +/* Edit ruleset */ + +.state-edit #edit-ruleset { + display: block; +} + +#edit-ruleset label { + color: var(--in-content-deemphasized-text); + display: block; +} + +#edit-ruleset label, #edit-buttonbar { + margin-top: var(--ruleset-vmargin); +} + +label#edit-enable { + display: flex; + align-items: center; +} + +/* No rulesets */ + +#no-rulesets { + max-width: 100%; + background-image: url(chrome://browser/skin/preferences/no-search-results.svg); + background-size: 275px 212px; + background-position: center center; + background-repeat: no-repeat; +} + +.state-noRulesets #no-rulesets { + display: block; +} diff --git a/browser/components/rulesets/content/aboutRulesets.html b/browser/components/rulesets/content/aboutRulesets.html new file mode 100644 index 0000000000000..d5b03435b1e72 --- /dev/null +++ b/browser/components/rulesets/content/aboutRulesets.html @@ -0,0 +1,110 @@ +<!-- Copyright (c) 2022, The Tor Project, Inc. --> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" /> + <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"> + <link rel="stylesheet" href="chrome://browser/content/rulesets/aboutRulesets.css"> + </head> + <body> + <!-- Warning --> + <div id="warning-wrapper"> + <div id="warning"> + <h1 id="warning-title"></h1> + <p id="warning-description"></p> + <p> + <label> + <input id="warning-enable-checkbox" type="checkbox" checked="checked"> + <span id="warning-enable-label"></span> + </label> + </p> + <div id="warning-buttonbar"> + <button id="warning-button" autofocus="autofocus"></button> + </div> + </div> + </div> + + <div id="main-content"> + <!-- Ruleset list --> + <aside> + <div id="ruleset-heading"></div> + <div id="ruleset-list-container"> + <div id="ruleset-list-empty"> + <p id="ruleset-list-empty-title"></p> + <p id="ruleset-list-empty-description"></p> + </div> + <ul id="ruleset-list"> + <li id="ruleset-template"> + <div class="icon"> + </div> + <div> + <div class="name"></div> + <div class="description"></div> + </div> + </li> + </ul> + </div> + </aside> + + <!-- Ruleset details --> + <section id="ruleset-details"> + <div class="title"> + <h1 id="ruleset-title"></h1> + <button id="ruleset-edit" class="ghost-button"></button> + </div> + <dl> + <dt id="ruleset-jwk-label"></dt> + <dd id="ruleset-jwk-value"></dd> + <dt id="ruleset-path-prefix-label"></dt> + <dd> + <a id="ruleset-path-prefix-value" target="_blank"></a> + </dd> + <dt id="ruleset-scope-label"></dt> + <dd id="ruleset-scope-value"></dd> + </dl> + <label id="ruleset-enable"> + <input type="checkbox" id="ruleset-enable-checkbox"> + <span id="ruleset-enable-label"></span> + </label> + <div id="ruleset-buttonbar"> + <button id="ruleset-update-button"></button> + </div> + <hr> + <p id="ruleset-updated"></p> + </section> + + <!-- Edit ruleset --> + <section id="edit-ruleset"> + <div class="title"> + <h1 id="edit-title"></h1> + </div> + <form id="edit-ruleset-form"> + <label> + <div id="edit-jwk-label"></div> + <textarea id="edit-jwk-textarea" rows="10"></textarea> + </label> + <label> + <div id="edit-path-prefix-label"></div> + <input id="edit-path-prefix-input" type="text"> + </label> + <label> + <div id="edit-scope-label"></div> + <input id="edit-scope-input" type="text"> + </label> + <label id="edit-enable"> + <input type="checkbox" id="edit-enable-checkbox"> + <span id="edit-enable-label"></span> + </label> + <div id="edit-buttonbar"> + <button id="edit-save" class="primary"></button> + <button id="edit-cancel"></button> + </div> + </form> + </section> + + <!-- No rulesets --> + <section id="no-rulesets"></section> + </div> + <script src="chrome://browser/content/rulesets/aboutRulesets.js"></script> + </body> +</html> diff --git a/browser/components/rulesets/content/aboutRulesets.js b/browser/components/rulesets/content/aboutRulesets.js new file mode 100644 index 0000000000000..4fabdca1b93d1 --- /dev/null +++ b/browser/components/rulesets/content/aboutRulesets.js @@ -0,0 +1,531 @@ +"use strict"; + +/* globals RPMAddMessageListener, RPMSendQuery, RPMSendAsyncMessage */ + +let TorStrings; + +const Orders = Object.freeze({ + Name: "name", + NameDesc: "name-desc", + LastUpdate: "last-update", +}); + +const States = Object.freeze({ + Warning: "warning", + Details: "details", + Edit: "edit", + NoRulesets: "noRulesets", +}); + +function setUpdateDate(ruleset, element) { + if (!ruleset.enabled) { + element.textContent = TorStrings.rulesets.disabled; + return; + } + if (!ruleset.currentTimestamp) { + element.textContent = TorStrings.rulesets.neverUpdated; + return; + } + + const formatter = new Intl.DateTimeFormat(navigator.languages, { + year: "numeric", + month: "long", + day: "numeric", + }); + element.textContent = TorStrings.rulesets.lastUpdated.replace( + "%S", + formatter.format(new Date(ruleset.currentTimestamp * 1000)) + ); +} + +class WarningState { + selectors = Object.freeze({ + wrapper: "#warning-wrapper", + title: "#warning-title", + description: "#warning-description", + enableCheckbox: "#warning-enable-checkbox", + enableLabel: "#warning-enable-label", + button: "#warning-button", + }); + + elements = Object.freeze({ + wrapper: document.querySelector(this.selectors.wrapper), + title: document.querySelector(this.selectors.title), + description: document.querySelector(this.selectors.description), + enableCheckbox: document.querySelector(this.selectors.enableCheckbox), + enableLabel: document.querySelector(this.selectors.enableLabel), + button: document.querySelector(this.selectors.button), + }); + + constructor() { + const elements = this.elements; + elements.title.textContent = TorStrings.rulesets.warningTitle; + elements.description.textContent = TorStrings.rulesets.warningDescription; + elements.enableLabel.textContent = TorStrings.rulesets.warningEnable; + elements.button.textContent = TorStrings.rulesets.warningButton; + elements.enableCheckbox.addEventListener( + "change", + this.onEnableChange.bind(this) + ); + elements.button.addEventListener("click", this.onButtonClick.bind(this)); + } + + show() { + this.elements.button.focus(); + } + + hide() {} + + onEnableChange() { + RPMSendAsyncMessage( + "rulesets:set-show-warning", + this.elements.enableCheckbox.checked + ); + } + + onButtonClick() { + gAboutRulesets.selectFirst(); + } +} + +class DetailsState { + selectors = Object.freeze({ + title: "#ruleset-title", + edit: "#ruleset-edit", + jwkLabel: "#ruleset-jwk-label", + jwkValue: "#ruleset-jwk-value", + pathPrefixLabel: "#ruleset-path-prefix-label", + pathPrefixValue: "#ruleset-path-prefix-value", + scopeLabel: "#ruleset-scope-label", + scopeValue: "#ruleset-scope-value", + enableCheckbox: "#ruleset-enable-checkbox", + enableLabel: "#ruleset-enable-label", + updateButton: "#ruleset-update-button", + updated: "#ruleset-updated", + }); + + elements = Object.freeze({ + title: document.querySelector(this.selectors.title), + edit: document.querySelector(this.selectors.edit), + jwkLabel: document.querySelector(this.selectors.jwkLabel), + jwkValue: document.querySelector(this.selectors.jwkValue), + pathPrefixLabel: document.querySelector(this.selectors.pathPrefixLabel), + pathPrefixValue: document.querySelector(this.selectors.pathPrefixValue), + scopeLabel: document.querySelector(this.selectors.scopeLabel), + scopeValue: document.querySelector(this.selectors.scopeValue), + enableCheckbox: document.querySelector(this.selectors.enableCheckbox), + enableLabel: document.querySelector(this.selectors.enableLabel), + updateButton: document.querySelector(this.selectors.updateButton), + updated: document.querySelector(this.selectors.updated), + }); + + constructor() { + const elements = this.elements; + elements.edit.textContent = TorStrings.rulesets.edit; + elements.edit.addEventListener("click", this.onEdit.bind(this)); + elements.jwkLabel.textContent = TorStrings.rulesets.jwk; + elements.pathPrefixLabel.textContent = TorStrings.rulesets.pathPrefix; + elements.scopeLabel.textContent = TorStrings.rulesets.scope; + elements.enableCheckbox.addEventListener( + "change", + this.onEnable.bind(this) + ); + elements.enableLabel.textContent = TorStrings.rulesets.enable; + elements.updateButton.textContent = TorStrings.rulesets.checkUpdates; + elements.updateButton.addEventListener("click", this.onUpdate.bind(this)); + } + + show(ruleset) { + const elements = this.elements; + elements.title.textContent = ruleset.name; + elements.jwkValue.textContent = JSON.stringify(ruleset.jwk); + elements.pathPrefixValue.setAttribute("href", ruleset.pathPrefix); + elements.pathPrefixValue.textContent = ruleset.pathPrefix; + elements.scopeValue.textContent = ruleset.scope; + elements.enableCheckbox.checked = ruleset.enabled; + if (ruleset.enabled) { + elements.updateButton.removeAttribute("disabled"); + } else { + elements.updateButton.setAttribute("disabled", "disabled"); + } + setUpdateDate(ruleset, elements.updated); + this._showing = ruleset; + + gAboutRulesets.list.setItemSelected(ruleset.name); + } + + hide() { + this._showing = null; + } + + onEdit() { + gAboutRulesets.setState(States.Edit, this._showing); + } + + async onEnable() { + await RPMSendAsyncMessage("rulesets:enable-channel", { + name: this._showing.name, + enabled: this.elements.enableCheckbox.checked, + }); + } + + async onUpdate() { + try { + await RPMSendQuery("rulesets:update-channel", this._showing.name); + } catch (err) { + console.error("Could not update the rulesets", err); + } + } +} + +class EditState { + selectors = Object.freeze({ + form: "#edit-ruleset-form", + title: "#edit-title", + nameGroup: "#edit-name-group", + nameLabel: "#edit-name-label", + nameInput: "#edit-name-input", + jwkLabel: "#edit-jwk-label", + jwkTextarea: "#edit-jwk-textarea", + pathPrefixLabel: "#edit-path-prefix-label", + pathPrefixInput: "#edit-path-prefix-input", + scopeLabel: "#edit-scope-label", + scopeInput: "#edit-scope-input", + enableCheckbox: "#edit-enable-checkbox", + enableLabel: "#edit-enable-label", + save: "#edit-save", + cancel: "#edit-cancel", + }); + + elements = Object.freeze({ + form: document.querySelector(this.selectors.form), + title: document.querySelector(this.selectors.title), + jwkLabel: document.querySelector(this.selectors.jwkLabel), + jwkTextarea: document.querySelector(this.selectors.jwkTextarea), + pathPrefixLabel: document.querySelector(this.selectors.pathPrefixLabel), + pathPrefixInput: document.querySelector(this.selectors.pathPrefixInput), + scopeLabel: document.querySelector(this.selectors.scopeLabel), + scopeInput: document.querySelector(this.selectors.scopeInput), + enableCheckbox: document.querySelector(this.selectors.enableCheckbox), + enableLabel: document.querySelector(this.selectors.enableLabel), + save: document.querySelector(this.selectors.save), + cancel: document.querySelector(this.selectors.cancel), + }); + + constructor() { + const elements = this.elements; + elements.jwkLabel.textContent = TorStrings.rulesets.jwk; + elements.jwkTextarea.setAttribute( + "placeholder", + TorStrings.rulesets.jwkPlaceholder + ); + elements.pathPrefixLabel.textContent = TorStrings.rulesets.pathPrefix; + elements.pathPrefixInput.setAttribute( + "placeholder", + TorStrings.rulesets.pathPrefixPlaceholder + ); + elements.scopeLabel.textContent = TorStrings.rulesets.scope; + elements.scopeInput.setAttribute( + "placeholder", + TorStrings.rulesets.scopePlaceholder + ); + elements.enableLabel.textContent = TorStrings.rulesets.enable; + elements.save.textContent = TorStrings.rulesets.save; + elements.save.addEventListener("click", this.onSave.bind(this)); + elements.cancel.textContent = TorStrings.rulesets.cancel; + elements.cancel.addEventListener("click", this.onCancel.bind(this)); + } + + show(ruleset) { + const elements = this.elements; + elements.form.reset(); + elements.title.textContent = ruleset.name; + elements.jwkTextarea.value = JSON.stringify(ruleset.jwk); + elements.pathPrefixInput.value = ruleset.pathPrefix; + elements.scopeInput.value = ruleset.scope; + elements.enableCheckbox.checked = ruleset.enabled; + this._editing = ruleset; + } + + hide() { + this.elements.form.reset(); + this._editing = null; + } + + async onSave(e) { + e.preventDefault(); + const elements = this.elements; + + let valid = true; + const name = this._editing.name; + + let jwk; + try { + jwk = JSON.parse(elements.jwkTextarea.value); + await crypto.subtle.importKey( + "jwk", + jwk, + { + name: "RSA-PSS", + saltLength: 32, + hash: { name: "SHA-256" }, + }, + true, + ["verify"] + ); + elements.jwkTextarea.setCustomValidity(""); + } catch (err) { + console.error("Invalid JSON or invalid JWK", err); + elements.jwkTextarea.setCustomValidity(TorStrings.rulesets.jwkInvalid); + valid = false; + } + + const pathPrefix = elements.pathPrefixInput.value.trim(); + try { + const url = new URL(pathPrefix); + if (url.protocol !== "http:" && url.protocol !== "https:") { + elements.pathPrefixInput.setCustomValidity( + TorStrings.rulesets.pathPrefixInvalid + ); + valid = false; + } else { + elements.pathPrefixInput.setCustomValidity(""); + } + } catch (err) { + console.error("The path prefix is not a valid URL", err); + elements.pathPrefixInput.setCustomValidity( + TorStrings.rulesets.pathPrefixInvalid + ); + valid = false; + } + + let scope; + try { + scope = new RegExp(elements.scopeInput.value.trim()); + elements.scopeInput.setCustomValidity(""); + } catch (err) { + elements.scopeInput.setCustomValidity(TorStrings.rulesets.scopeInvalid); + valid = false; + } + + if (!valid) { + return; + } + + const enabled = elements.enableCheckbox.checked; + + const rulesetData = { name, jwk, pathPrefix, scope, enabled }; + const ruleset = await RPMSendQuery("rulesets:set-channel", rulesetData); + gAboutRulesets.setState(States.Details, ruleset); + if (enabled) { + try { + await RPMSendQuery("rulesets:update-channel", name); + } catch (err) { + console.warn("Could not update the ruleset after adding it", err); + } + } + } + + onCancel(e) { + e.preventDefault(); + if (this._editing === null) { + gAboutRulesets.selectFirst(); + } else { + gAboutRulesets.setState(States.Details, this._editing); + } + } +} + +class NoRulesetsState { + show() {} + hide() {} +} + +class RulesetList { + selectors = Object.freeze({ + heading: "#ruleset-heading", + list: "#ruleset-list", + emptyContainer: "#ruleset-list-empty", + emptyTitle: "#ruleset-list-empty-title", + emptyDescription: "#ruleset-list-empty-description", + itemTemplate: "#ruleset-template", + itemName: ".name", + itemDescr: ".description", + }); + + elements = Object.freeze({ + heading: document.querySelector(this.selectors.heading), + list: document.querySelector(this.selectors.list), + emptyContainer: document.querySelector(this.selectors.emptyContainer), + emptyTitle: document.querySelector(this.selectors.emptyTitle), + emptyDescription: document.querySelector(this.selectors.emptyDescription), + itemTemplate: document.querySelector(this.selectors.itemTemplate), + }); + + nameAttribute = "data-name"; + + rulesets = []; + + constructor() { + const elements = this.elements; + + // Header + elements.heading.textContent = TorStrings.rulesets.rulesets; + // Empty + elements.emptyTitle.textContent = TorStrings.rulesets.noRulesets; + elements.emptyDescription.textContent = TorStrings.rulesets.noRulesetsDescr; + + RPMAddMessageListener( + "rulesets:channels-change", + this.onRulesetsChanged.bind(this) + ); + } + + getSelectedRuleset() { + const name = this.elements.list + .querySelector(".selected") + ?.getAttribute(this.nameAttribute); + for (const ruleset of this.rulesets) { + if (ruleset.name == name) { + return ruleset; + } + } + return null; + } + + isEmpty() { + return !this.rulesets.length; + } + + async update() { + this.rulesets = await RPMSendQuery("rulesets:get-channels"); + await this._populateRulesets(); + } + + setItemSelected(name) { + name = name.replace(/["\]/g, "\$&"); + const item = this.elements.list.querySelector( + `.item[${this.nameAttribute}="${name}"]` + ); + this._selectItem(item); + } + + async _populateRulesets() { + if (this.isEmpty()) { + this.elements.emptyContainer.classList.remove("hidden"); + } else { + this.elements.emptyContainer.classList.add("hidden"); + } + + const list = this.elements.list; + const selName = list + .querySelector(".item.selected") + ?.getAttribute(this.nameAttribute); + const items = list.querySelectorAll(".item"); + for (const item of items) { + item.remove(); + } + + for (const ruleset of this.rulesets) { + const item = this._addItem(ruleset); + if (ruleset.name === selName) { + this._selectItem(item); + } + } + } + + _addItem(ruleset) { + const item = this.elements.itemTemplate.cloneNode(true); + item.removeAttribute("id"); + item.classList.add("item"); + item.querySelector(this.selectors.itemName).textContent = ruleset.name; + const descr = item.querySelector(this.selectors.itemDescr); + if (ruleset.enabled) { + setUpdateDate(ruleset, descr); + } else { + descr.textContent = TorStrings.rulesets.disabled; + item.classList.add("disabled"); + } + item.setAttribute(this.nameAttribute, ruleset.name); + item.addEventListener("click", () => { + this.onRulesetClick(ruleset); + }); + this.elements.list.append(item); + return item; + } + + _selectItem(item) { + this.elements.list.querySelector(".selected")?.classList.remove("selected"); + item?.classList.add("selected"); + } + + onRulesetClick(ruleset) { + gAboutRulesets.setState(States.Details, ruleset); + } + + onRulesetsChanged(data) { + this.rulesets = data.data; + this._populateRulesets(); + const selected = this.getSelectedRuleset(); + if (selected !== null) { + gAboutRulesets.setState(States.Details, selected); + } + } +} + +class AboutRulesets { + _state = null; + + async init() { + const args = await RPMSendQuery("rulesets:get-init-args"); + TorStrings = args.TorStrings; + const showWarning = args.showWarning; + + this.list = new RulesetList(); + this._states = {}; + this._states[States.Warning] = new WarningState(); + this._states[States.Details] = new DetailsState(); + this._states[States.Edit] = new EditState(); + this._states[States.NoRulesets] = new NoRulesetsState(); + + await this.refreshRulesets(); + + if (showWarning) { + this.setState(States.Warning); + } else { + this.selectFirst(); + } + } + + setState(state, ...args) { + document.querySelector("body").className = `state-${state}`; + this._state?.hide(); + this._state = this._states[state]; + this._state.show(...args); + } + + async refreshRulesets() { + await this.list.update(); + if (this._state === this._states[States.Details]) { + const ruleset = this.list.getSelectedRuleset(); + if (ruleset !== null) { + this.setState(States.Details, ruleset); + } else { + this.selectFirst(); + } + } else if (this.list.isEmpty()) { + this.setState(States.NoRulesets); + } + } + + selectFirst() { + if (this.list.isEmpty()) { + this.setState(States.NoRulesets); + } else { + this.setState("details", this.list.rulesets[0]); + } + } +} + +const gAboutRulesets = new AboutRulesets(); +gAboutRulesets.init(); diff --git a/browser/components/rulesets/content/securedrop.svg b/browser/components/rulesets/content/securedrop.svg new file mode 100644 index 0000000000000..69cd584ac1edf --- /dev/null +++ b/browser/components/rulesets/content/securedrop.svg @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 23.0.5, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + version="1.1" + id="Layer_1" + x="0px" + y="0px" + viewBox="0 0 423.3 423.3" + xml:space="preserve" + width="423.29999" + height="423.29999" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/%22%3E<defs + id="defs49"> + + + + + + + + + + + <defs + id="defs24"> + <filter + id="Adobe_OpacityMaskFilter_1_" + filterUnits="userSpaceOnUse" + x="-66" + y="-0.89999998" + width="183.3" + height="318.20001"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix21" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="-66" + y="-0.9" + width="183.3" + height="318.2" + id="mask-4_1_"> + <g + class="st4" + id="g27"> + <polygon + id="path-3_1_" + class="st2" + points="117.3,-0.9 117.3,317.3 -66,317.3 -66,-0.9 " /> + </g> + </mask> + + + + <defs + id="defs36"> + <filter + id="Adobe_OpacityMaskFilter_2_" + filterUnits="userSpaceOnUse" + x="-66" + y="-1" + width="366.29999" + height="211.3"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix33" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="-66" + y="-1" + width="366.3" + height="211.3" + id="mask-6_1_"> + <g + class="st6" + id="g39"> + <polygon + id="path-5_1_" + class="st2" + points="300.3,-1 300.3,210.3 -66,210.3 -66,-1 " /> + </g> + </mask> + + + + + <defs + id="defs11"> + <filter + id="Adobe_OpacityMaskFilter" + filterUnits="userSpaceOnUse" + x="-65.199997" + y="-0.89999998" + width="183.5" + height="318.20001"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix8" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="-65.2" + y="-0.9" + width="183.5" + height="318.2" + id="mask-2_1_"> + <g + class="st1" + id="g14"> + <polygon + id="path-1_1_" + class="st2" + points="-65.2,317.3 -65.2,-0.9 118.3,-0.9 118.3,317.3 " /> + </g> + </mask> + + + + </defs> +<style + type="text/css" + id="style2"> + .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#055FB4;} + .st1{filter:url(#Adobe_OpacityMaskFilter);} + .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;} + .st3{mask:url(#mask-2_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#055FB4;} + .st4{filter:url(#Adobe_OpacityMaskFilter_1_);} + .st5{mask:url(#mask-4_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#093D70;} + .st6{filter:url(#Adobe_OpacityMaskFilter_2_);} + .st7{mask:url(#mask-6_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#2E8AE8;} +</style> +<title + id="title4">Big Logo HP</title> +<circle + style="fill:#ffffff;stroke:none;stroke-width:2.66667" + id="path1626" + r="176.46054" + cy="211.64999" + cx="211.64999" /><path + id="Fill-1" + class="st0" + d="m 327.99999,225.5 -41.8,23.9 0.2,58.5 42.5,-23.6 c 5.1,-2.8 8.3,-8.3 8.3,-14 v -39.7 c -0.2,-0.9 -0.2,-2.1 -0.9,-2.8 -1.9,-2.8 -5.6,-3.9 -8.3,-2.3" /><path + id="Fill-3" + class="st3" + d="m 85.9,173.2 c 0,9.9 -5.3,19 -14,24.1 l -90.7,52.3 V 127.3 l 84,-48.6 c 2.1,-1.1 4.4,-1.8 6.9,-1.8 7.6,0 13.8,6.2 13.8,13.8 z M -65.2,104.9 V 317.3 L 118.3,211.5 V -0.9 Z" + mask="url(#mask-2_1_)" + transform="translate(276.49999,106)" /><path + id="Fill-7" + class="st5" + d="M 71.7,158.3 3.3,118.8 v 14 l 68.4,39.5 v 73.9 L -22.2,192 v -30.1 l 64,37.2 v -13.8 l -64,-37.2 V 75 l 93.8,54.2 v 29.1 z M -66,-0.9 V 211.5 L 117.3,317.3 V 104.9 Z" + mask="url(#mask-4_1_)" + transform="translate(94.499994,106)" /><path + id="Fill-10" + class="st7" + d="m 135,143.2 55.3,-31.1 -62.2,-17.2 c 1.1,-2.1 1.8,-4.4 1.8,-6.6 0,-11.5 -16.7,-21.1 -37.4,-21.1 -20.6,0 -37.4,9.4 -37.4,21.1 0,11.7 16.7,21.1 37.4,21.1 2.8,0 5.3,-0.2 8,-0.5 z M 117,210.3 -66,104.7 117,-1 300.3,104.7 Z" + mask="url(#mask-6_1_)" + transform="translate(94.499994,1)" /> +<metadata + id="metadata866">rdf:RDF<cc:Work + rdf:about="">dc:titleBig Logo HP</dc:title></cc:Work></rdf:RDF></metadata></svg> diff --git a/browser/components/rulesets/jar.mn b/browser/components/rulesets/jar.mn new file mode 100644 index 0000000000000..e0b67442d89c0 --- /dev/null +++ b/browser/components/rulesets/jar.mn @@ -0,0 +1,5 @@ +browser.jar: + content/browser/rulesets/aboutRulesets.css (content/aboutRulesets.css) + content/browser/rulesets/aboutRulesets.html (content/aboutRulesets.html) + content/browser/rulesets/aboutRulesets.js (content/aboutRulesets.js) + content/browser/rulesets/securedrop.svg (content/securedrop.svg) diff --git a/browser/components/rulesets/moz.build b/browser/components/rulesets/moz.build new file mode 100644 index 0000000000000..daec4c3025244 --- /dev/null +++ b/browser/components/rulesets/moz.build @@ -0,0 +1,6 @@ +JAR_MANIFESTS += ['jar.mn'] + +EXTRA_JS_MODULES += [ + 'RulesetsChild.jsm', + 'RulesetsParent.jsm', +] diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index 69e4f547e8eb3..d1348783da12e 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -652,3 +652,14 @@ if link_service: 'singleton': True, }, **link_service) ] + +Classes += [ + { + 'cid': '{0df7784b-7316-486d-bc99-bf47b7a05974}', + 'contract_ids': ['@torproject.org/onion-alias-service;1'], + 'singleton': True, + 'type': 'IOnionAliasService', + 'constructor': 'torproject::OnionAliasService::GetSingleton', + 'headers': ['torproject/OnionAliasService.h'], + }, +] diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 3dce83342516b..e818a481595e3 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -842,4 +842,14 @@ } \ }
+// Onion alias service implementing IOnionAliasService +#define ONIONALIAS_CONTRACTID \ + "@torproject.org/onion-alias-service;1" +#define ONIONALIAS_CID \ + { /* 0df7784b-7316-486d-bc99-bf47b7a05974 */ \ + 0x0df7784b, 0x7316, 0x486d, { \ + 0xbc, 0x99, 0xbf, 0x47, 0xb7, 0xa0, 0x59, 0x74 \ + } \ + } + #endif // nsNetCID_h__ diff --git a/netwerk/dns/IOnionAliasService.idl b/netwerk/dns/IOnionAliasService.idl new file mode 100644 index 0000000000000..692c74b917930 --- /dev/null +++ b/netwerk/dns/IOnionAliasService.idl @@ -0,0 +1,34 @@ +#include "nsISupports.idl" + +/** + * Service used for .tor.onion aliases. + * It stores the real .onion address that correspond to .tor.onion addresses, + * so that both C++ code and JS can access them. + */ +[scriptable, uuid(0df7784b-7316-486d-bc99-bf47b7a05974)] +interface IOnionAliasService : nsISupports +{ + /** + * Add a new Onion alias + * @param aShortHostname + * The short hostname that is being rewritten + * @param aLongHostname + * The complete onion v3 hostname + */ + void addOnionAlias(in ACString aShortHostname, + in ACString aLongHostname); + + /** + * Return an onion alias. + * + * @param aShortHostname + * The .tor.onion hostname to resolve + * @return a v3 address, or the input, if the short hostname is not known + */ + ACString getOnionAlias(in ACString aShortHostname); + + /** + * Clears Onion aliases. + */ + void clearOnionAliases(); +}; diff --git a/netwerk/dns/OnionAliasService.cpp b/netwerk/dns/OnionAliasService.cpp new file mode 100644 index 0000000000000..2d24bb68023b4 --- /dev/null +++ b/netwerk/dns/OnionAliasService.cpp @@ -0,0 +1,94 @@ +#include "torproject/OnionAliasService.h" + +#include "nsUnicharUtils.h" + +/** + * Check if a hostname is a valid Onion v3 hostname. + * + * @param aHostname + * The hostname to verify. It is not a const reference because any + * uppercase character will be transformed to lowercase during the + * verification. + * @return Tells whether the input string is an Onion v3 address + */ +static bool ValidateOnionV3(nsACString &aHostname) +{ + constexpr nsACString::size_type v3Length = 56 + 6; + if (aHostname.Length() != v3Length) { + return false; + } + ToLowerCase(aHostname); + if (!StringEndsWith(aHostname, ".onion"_ns)) { + return false; + } + + char* cur = aHostname.BeginWriting(); + // We have already checked that it ends by ".onion" + const char* end = aHostname.EndWriting() - 6; + for (; cur < end; ++cur) { + if (!(islower(*cur) || ('2' <= *cur && *cur <= '7'))) { + return false; + } + } + + return true; +} + +namespace torproject { + +NS_IMPL_ISUPPORTS(OnionAliasService, IOnionAliasService) + +static mozilla::StaticRefPtr<OnionAliasService> gOAService; + +// static +already_AddRefed<IOnionAliasService> OnionAliasService::GetSingleton() { + if (gOAService) { + return do_AddRef(gOAService); + } + + gOAService = new OnionAliasService(); + ClearOnShutdown(&gOAService); + return do_AddRef(gOAService); +} + +NS_IMETHODIMP +OnionAliasService::AddOnionAlias(const nsACString& aShortHostname, + const nsACString& aLongHostname) { + nsAutoCString shortHostname; + ToLowerCase(aShortHostname, shortHostname); + mozilla::UniquePtr<nsAutoCString> longHostname = + mozilla::MakeUnique<nsAutoCString>(aLongHostname); + if (!longHostname) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!StringEndsWith(shortHostname, ".tor.onion"_ns) || + !ValidateOnionV3(*longHostname)) { + return NS_ERROR_INVALID_ARG; + } + mozilla::MutexAutoLock lock(mLock); + mOnionAliases.InsertOrUpdate(shortHostname, std::move(longHostname)); + return NS_OK; +} + +NS_IMETHODIMP +OnionAliasService::GetOnionAlias(const nsACString& aShortHostname, nsACString& aLongHostname) +{ + aLongHostname = aShortHostname; + if (StringEndsWith(aShortHostname, ".tor.onion"_ns)) { + nsAutoCString* alias = nullptr; + if (mOnionAliases.Get(aShortHostname, &alias)) { + // We take for granted aliases have already been validated + aLongHostname.Assign(*alias); + } + } + return NS_OK; +} + +NS_IMETHODIMP +OnionAliasService::ClearOnionAliases() { + mozilla::MutexAutoLock lock(mLock); + mOnionAliases.Clear(); + return NS_OK; +} + +} // namespace torproject diff --git a/netwerk/dns/OnionAliasService.h b/netwerk/dns/OnionAliasService.h new file mode 100644 index 0000000000000..913157e61ccb6 --- /dev/null +++ b/netwerk/dns/OnionAliasService.h @@ -0,0 +1,36 @@ +#ifndef OnionAliasService_h_ +#define OnionAliasService_h_ + +#include "ScopedNSSTypes.h" +#include "IOnionAliasService.h" + +namespace torproject { + +class OnionAliasService final : public IOnionAliasService { +public: + NS_DECL_ISUPPORTS + NS_DECL_IONIONALIASSERVICE + + static already_AddRefed<IOnionAliasService> GetSingleton(); + +private: + + OnionAliasService() = default; + OnionAliasService(const OnionAliasService&) = delete; + OnionAliasService(OnionAliasService&&) = delete; + OnionAliasService &operator=(const OnionAliasService&) = delete; + OnionAliasService &operator=(OnionAliasService&&) = delete; + virtual ~OnionAliasService() = default; + + // mLock protects access to mOnionAliases + mozilla::Mutex mLock{"OnionAliasService.mLock"}; + + // AutoCStrings have a 64 byte buffer, so it is advised not to use them for + // long storage. However, it is enough to contain onion addresses, so we use + // them instead, and avoid allocating on heap for each alias + nsClassHashtable<nsCStringHashKey, nsAutoCString> mOnionAliases; +}; + +} + +#endif // OnionAliasService_h_ diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat index 4c0c6e9a98695..bdad4efc7f99b 100644 --- a/netwerk/dns/effective_tld_names.dat +++ b/netwerk/dns/effective_tld_names.dat @@ -5527,6 +5527,8 @@ pro.om
// onion : https://tools.ietf.org/html/rfc7686 onion +tor.onion +securedrop.tor.onion
// org : https://en.wikipedia.org/wiki/.org org diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build index 1498dd2ceb9e0..560916367e825 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -110,3 +110,7 @@ USE_LIBS += ["icu"]
if CONFIG["CC_TYPE"] in ("clang", "gcc"): CXXFLAGS += ["-Wno-error=shadow"] + +XPIDL_SOURCES += ["IOnionAliasService.idl"] +UNIFIED_SOURCES += ["OnionAliasService.cpp"] +EXPORTS.torproject += ["OnionAliasService.h"] diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp index f9fc29552aceb..a9dc8f9dde119 100644 --- a/netwerk/socket/nsSOCKSIOLayer.cpp +++ b/netwerk/socket/nsSOCKSIOLayer.cpp @@ -25,6 +25,8 @@ #include "mozilla/net/DNS.h" #include "mozilla/Unused.h"
+#include "IOnionAliasService.h" + using mozilla::LogLevel; using namespace mozilla::net;
@@ -861,11 +863,23 @@ PRStatus nsSOCKSSocketInfo::WriteV5ConnectRequest() { // Add the address to the SOCKS 5 request. SOCKS 5 supports several // address types, so we pick the one that works best for us. if (proxy_resolve) { - // Add the host name. Only a single byte is used to store the length, - // so we must prevent long names from being used. - buf2 = buf.WriteUint8(0x03) // addr type -- domainname - .WriteUint8(mDestinationHost.Length()) // name length - .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname + if (StringEndsWith(mDestinationHost, ".tor.onion"_ns)) { + nsAutoCString realHost; + nsCOMPtr<IOnionAliasService> oas = do_GetService(ONIONALIAS_CID); + if (NS_FAILED(oas->GetOnionAlias(mDestinationHost, realHost))) { + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + buf2 = buf.WriteUint8(0x03) + .WriteUint8(realHost.Length()) + .WriteString<MAX_HOSTNAME_LEN>(realHost); + } else { + // Add the host name. Only a single byte is used to store the length, + // so we must prevent long names from being used. + buf2 = buf.WriteUint8(0x03) // addr type -- domainname + .WriteUint8(mDestinationHost.Length()) // name length + .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname + } if (!buf2) { LOGERROR(("socks5: destination host name is too long!")); HandshakeFinished(PR_BAD_ADDRESS_ERROR); diff --git a/security/manager/ssl/SSLServerCertVerification.cpp b/security/manager/ssl/SSLServerCertVerification.cpp index c20c1c0583784..0a84aecc6c724 100644 --- a/security/manager/ssl/SSLServerCertVerification.cpp +++ b/security/manager/ssl/SSLServerCertVerification.cpp @@ -137,6 +137,8 @@ #include "sslerr.h" #include "sslexp.h"
+#include "IOnionAliasService.h" + extern mozilla::LazyLogModule gPIPNSSLog;
using namespace mozilla::pkix; @@ -1037,6 +1039,13 @@ SECStatus SSLServerCertVerificationJob::Dispatch( return SECWouldBlock; }
+void SSLServerCertVerificationJob::FixOnionAlias() { + if (StringEndsWith(mHostName, ".tor.onion"_ns)) { + nsCOMPtr<IOnionAliasService> oas = do_GetService(ONIONALIAS_CID); + oas->GetOnionAlias(mHostName, mHostName); + } +} + NS_IMETHODIMP SSLServerCertVerificationJob::Run() { // Runs on a cert verification thread and only on parent process. diff --git a/security/manager/ssl/SSLServerCertVerification.h b/security/manager/ssl/SSLServerCertVerification.h index a315b30835d48..9ae7ac47b0566 100644 --- a/security/manager/ssl/SSLServerCertVerification.h +++ b/security/manager/ssl/SSLServerCertVerification.h @@ -141,7 +141,9 @@ class SSLServerCertVerificationJob : public Runnable { mStapledOCSPResponse(std::move(stapledOCSPResponse)), mSCTsFromTLSExtension(std::move(sctsFromTLSExtension)), mDCInfo(std::move(dcInfo)), - mResultTask(aResultTask) {} + mResultTask(aResultTask) { FixOnionAlias(); } + + void FixOnionAlias();
uint64_t mAddrForLogging; void* mPinArg; diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index c195ab688b236..f9cbf0badf69a 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -211,6 +211,20 @@ let RemotePageAccessManager = { ], RPMRecordTelemetryEvent: ["*"], }, + "about:rulesets": { + RPMAddMessageListener: ["rulesets:channels-change"], + RPMSendAsyncMessage: [ + "rulesets:delete-channel", + "rulesets:enable-channel", + "rulesets:set-show-warning", + ], + RPMSendQuery: [ + "rulesets:get-channels", + "rulesets:get-init-args", + "rulesets:set-channel", + "rulesets:update-channel", + ], + }, "about:tabcrashed": { RPMSendAsyncMessage: ["Load", "closeTab", "restoreTab", "restoreAll"], RPMAddMessageListener: ["*"],
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit 2ccfccba9353533259fec040b45eaddc43ade850 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu May 5 20:15:01 2022 +0200
Bug 11698: Incorporate Tor Browser Manual pages into Tor Browser
This patch associates the about:manual page to a translated page that must be injected to browser/omni.ja after the build. The content must be placed in chrome/browser/content/browser/manual/, so that is then available at chrome://browser/content/manual/. We preferred giving absolute freedom to the web team, rather than having to change the patch in case of changes on the documentation. --- browser/base/content/browser-siteIdentity.js | 4 +- browser/components/about/AboutRedirector.cpp | 61 ++++++++++++++++++++++++++++ browser/components/about/components.conf | 1 + 3 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 5e70d458094cf..b7d59db3dd34d 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -58,8 +58,8 @@ var gIdentityHandler = { * the browser UI. */ _secureInternalPages: (AppConstants.TOR_BROWSER_UPDATE ? - /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : - /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i), + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|manual|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|manual|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i),
/** * Whether the established HTTPS connection is considered "broken". diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 57c4d40c8ef56..b46811eaadf74 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -18,6 +18,11 @@ #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h"
+// For Tor Browser manual +#include "nsTHashSet.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/Omnijar.h" + namespace mozilla { namespace browser {
@@ -136,6 +141,8 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + // The correct URI must be obtained by GetManualChromeURI + {"manual", "about:blank", nsIAboutModule::ALLOW_SCRIPT}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { @@ -152,6 +159,52 @@ static nsAutoCString GetAboutModuleName(nsIURI* aURI) { return path; }
+static nsTHashSet<nsCStringHashKey> GetManualLocales() +{ + nsTHashSet<nsCStringHashKey> locales; + RefPtr<nsZipArchive> zip = Omnijar::GetReader(Omnijar::APP); + UniquePtr<nsZipFind> find; + const nsAutoCString prefix("chrome/browser/content/browser/manual/"); + nsAutoCString needle = prefix; + needle.Append("*.html"); + if (NS_SUCCEEDED(zip->FindInit(needle.get(), getter_Transfers(find)))) { + const char* entryName; + uint16_t entryNameLen; + while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) { + // 5 is to remove the final `.html` + const size_t length = entryNameLen - prefix.Length() - 5; + locales.Insert(nsAutoCString(entryName + prefix.Length(), length)); + } + } + return locales; +} + +static nsAutoCString GetManualChromeURI() { + static nsTHashSet<nsCStringHashKey> locales = GetManualLocales(); + + nsAutoCString reqLocale; + intl::LocaleService::GetInstance()->GetAppLocaleAsBCP47(reqLocale); + // Check every time the URL is needed in case the lang has been changed. + // We do not provide multi-language builds at the moment, so this should not + // happen, at least in Tor Browser desktop, but we prepared the patch to be + // ready also in such a case. + if (!locales.Contains(reqLocale) && reqLocale.Length() > 2 && + reqLocale[2] == '-') { + // At the moment, codes in our manual output are either 2 letters (en) or + // 5 letters (pt-BR) + reqLocale.SetLength(2); + } + if (!locales.Contains(reqLocale)) { + reqLocale = "en"; + } + + // %s is the language + constexpr char model[] = "chrome://browser/content/manual/%s.html"; + nsAutoCString url; + url.AppendPrintf(model, reqLocale.get()); + return url; +} + NS_IMETHODIMP AboutRedirector::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** result) { @@ -205,6 +258,10 @@ AboutRedirector::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, NS_ENSURE_SUCCESS(rv, rv); }
+ if (path.EqualsLiteral("manual")) { + url = GetManualChromeURI(); + } + // fall back to the specified url in the map if (url.IsEmpty()) { url.AssignASCII(redir.url); @@ -263,6 +320,10 @@ AboutRedirector::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
nsAutoCString name = GetAboutModuleName(aURI);
+ if (name.EqualsLiteral("manual")) { + return NS_NewURI(chromeURI, GetManualChromeURI()); + } + for (const auto& redir : kRedirMap) { if (name.Equals(redir.id)) { return NS_NewURI(chromeURI, redir.url); diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index df7c05b3193d5..e8034b31d8699 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -12,6 +12,7 @@ pages = [ 'home', 'logins', 'loginsimportreport', + 'manual', 'newtab', 'pocket-home', 'pocket-saved',
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit e10b30227eb46556f858e145d05d87006bff2fce Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Fri Jun 10 18:58:44 2022 +0200
fixup! Base Browser's .mozconfigs. --- browser/config/mozconfigs/base-browser | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/browser/config/mozconfigs/base-browser b/browser/config/mozconfigs/base-browser index 1fae066a04a71..1896b995a8d47 100644 --- a/browser/config/mozconfigs/base-browser +++ b/browser/config/mozconfigs/base-browser @@ -26,6 +26,8 @@ ac_add_options --disable-parental-controls # Let's make sure no preference is enabling either Adobe's or Google's CDM. ac_add_options --disable-eme ac_add_options --enable-proxy-bypass-protection +# See bugs #30575 and #32418: system policies are harmful either because they +# could allow proxy bypass, and override a number of other preferences we set ac_add_options --disable-system-policies
# Disable telemetry
tor-commits@lists.torproject.org