This is an automated email from the git hooks/post-receive script.
richard pushed a change to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
at 132fd7cc5ad6 Bug 11698: Incorporate Tor Browser Manual pages into Tor Browser
This branch includes the following new commits:
new 5369fddc5561 Firefox preference overrides. new cf65343370a8 Bug 41043: Hardcode the UI font on Linux new 32864a3269a2 Bug 30605: Honor privacy.spoof_english in Android new a4fb3f44f3b6 Bug 40199: Avoid using system locale for intl.accept_languages in GeckoView new 189029539685 Bug 40171: Make WebRequest and GeckoWebExecutor First-Party aware new 0a3ce2b7142d Bug 26345: Hide tracking protection UI new 70a342858d62 Bug 9173: Change the default Firefox profile directory to be relative. new 0b1c08d15252 Bug 18800: Remove localhost DNS lookup in nsProfileLock.cpp new 07c909ae61cf Bug 27604: Fix addon issues when moving the profile directory new 6c13f9740514 Bug 13028: Prevent potential proxy bypass cases. new 6c1ed314b3b6 Bug 11641: Disable remoting by default. new feed12a52c31 Bug 23104: Add a default line height compensation new 20484f4dd459 Bug 40309: Avoid using regional OS locales new 33fe51f2a80a Bug 40432: Prevent probing installed applications new e97df7087e3f Bug 32220: Improve the letterboxing experience new 968f12feb41d Bug 40069: Add helpers for message passing with extensions new f9b149daef54 Bug 40253: Explicitly allow NoScript in Private Browsing mode. new 8e758c6e952c Bug 40925: Implemented the Security Level component new 3d5527beccc5 Bug 40926: Implemented the New Identity feature new 4df1b05a15fb Bug 41089: Add tor-browser build scripts + Makefile to tor-browser new c8ec3849ef68 Bug 2176: Rebrand Firefox to TorBrowser new f14573b550ba Bring back old Firefox onboarding new 6975fd72b9c7 Bug 26961: New user onboarding. new 731b216f403e TB3: Tor Browser's official .mozconfigs. new db2e93d421bf Bug 40562: Added Tor-related preferences to 000-tor-browser.js new 6cd742c30806 Bug 13252: Do not store data in the app bundle new 2810ae48b167 Bug 40597: Implement TorSettings module new 99228a422585 Bug 10760: Integrate TorButton to TorBrowser core new 149a3ffb1fce Bug 28044: Integrate Tor Launcher into tor-browser new 892a822085a6 Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources. new eb0d02ede5d5 Add TorStrings module for localization new 8020ef3b23c1 Bug 14631: Improve profile access error messages. new ae81c697dfb6 Bug 40209: Implement Basic Crypto Safety new 2cce8efa3ced Bug 19273: Avoid JavaScript patching of the external app helper dialog. new 47154f90c98c Bug 40807: Added QRCode.js to toolkit/modules new dffd7bd7b066 Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection new ebd7d522fcd8 Bug 27476: Implement about:torconnect captive portal within Tor Browser new d6914de96414 Bug 12620: TorBrowser regression tests new e46aac3e9373 Bug 4234: Use the Firefox Update Process for Tor Browser. new d2a93a95b633 Bug 13379: Sign our MAR files. new 0b32c19df741 Bug 16940: After update, load local change notes. new 01d3c8d618db Bug 32658: Create a new MAR signing key new 9edcad9dce78 Omnibox: Add DDG, Startpage, Disconnect, Youtube, Twitter; remove Amazon, eBay, bing new e595af6f0f64 Bug 23247: Communicating security expectations for .onion new 491f7f18a59e Bug 30237: Add v3 onion services client authentication prompt new 2117e1429c87 Bug 21952: Implement Onion-Location new 301ab4aa8fe2 Bug 40458: Implement .tor.onion aliases new 132fd7cc5ad6 Bug 11698: Incorporate Tor Browser Manual pages into Tor Browser
The 48 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.
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 5369fddc5561613055e330f1eb70f0dbbf829798 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
Bug 41292: Disable moreFromMozilla in preferences page --- .eslintignore | 3 + browser/app/profile/001-base-profile.js | 667 ++++++++++++++++++++++++++++++++ browser/app/profile/firefox.js | 6 +- browser/installer/package-manifest.in | 1 + browser/moz.build | 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 + 9 files changed, 683 insertions(+), 3 deletions(-)
diff --git a/.eslintignore b/.eslintignore index 0cc0e1b0f0a7..7bb3f16a4a91 100644 --- a/.eslintignore +++ b/.eslintignore @@ -147,6 +147,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 000000000000..7ba669c37aa7 --- /dev/null +++ b/browser/app/profile/001-base-profile.js @@ -0,0 +1,667 @@ +// 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); + +// Require Safe Negotiation ( https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/27719 ) +// Blocks connections to servers that don't support RFC 5746 [2] as they're potentially vulnerable to a +// MiTM attack [3]. A server without RFC 5746 can be safe from the attack if it disables renegotiations +// but the problem is that the browser can't know that. Setting this pref to true is the only way for the +// browser to ensure there will be no unsafe renegotiations on the channel between the browser and the server +// [STATS] SSL Labs (July 2021) reports over 99% of top sites have secure renegotiation [4] +// [1] https://wiki.mozilla.org/Security:Renegotiation +// [2] https://datatracker.ietf.org/doc/html/rfc5746 +// [3] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555 +// [4] https://www.ssllabs.com/ssl-pulse/ +pref("security.ssl.require_safe_negotiation", true); + +// 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 activity stream/"Recommended by Pocket" in about:home (Bug #41029) +pref("browser.newtabpage.activity-stream.discoverystream.enabled", false); +pref("browser.newtabpage.activity-stream.feeds.section.topstories", false); + +// Disable moreFromMozilla pane in the preferences/settings (tor-browser#41292). +pref("browser.preferences.moreFromMozilla", 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", ""); + +// 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","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","new-window-button","save-page-button","print-bu [...] + +// 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, Kailasa, Lucida Grande, Menlo, Monaco, STHeiti, Tahoma, Thonburi, Times, Times New Roman, Verdana, STIX Math, Noto Sans Adlam, Noto Sans Armenian, Noto Sans Balinese, Noto Sans Bamum, Noto Sans Bassa Vah, Noto Sans Batak, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Buhid, Noto Sans Canadian Abor [...] + +// Armenian +pref("font.name-list.serif.x-armn", "Noto Serif Armenian, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-armn", "Noto Sans Armenian, Helvetica, Arial"); +pref("font.name-list.monospace.x-armn", "Noto Sans Armenian, Menlo, Courier New"); +// Bengali +pref("font.name-list.serif.x-beng", "Noto Serif Bengali, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-beng", "Noto Sans Bengali, Helvetica, Arial"); +pref("font.name-list.monospace.x-beng", "Noto Sans Bengali, Menlo, Courier New"); +// Canadian Aboriginal +pref("font.name-list.serif.x-cans", "Noto Serif Canadian Aboriginal, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-cans", "Noto Sans Canadian Aboriginal, Helvetica, Arial"); +pref("font.name-list.monospace.x-cans", "Noto Sans Canadian Aboriginal, Menlo, Courier New"); +// Devanagari +pref("font.name-list.serif.x-devanagari", "Noto Serif Devanagari, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-devanagari", "Noto Sans Devanagari, Helvetica, Arial"); +pref("font.name-list.monospace.x-devanagari", "Noto Sans Devanagari, Menlo, Courier New"); +// Ethiopic +pref("font.name-list.serif.x-ethi", "Noto Serif Ethiopic, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-ethi", "Noto Sans Ethiopic, Helvetica, Arial"); +pref("font.name-list.monospace.x-ethi", "Noto Sans Ethiopic, Menlo, Courier New"); +// Georgian +pref("font.name-list.serif.x-geor", "Noto Serif Georgian, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-geor", "Noto Sans Georgian, Helvetica, Arial"); +pref("font.name-list.monospace.x-geor", "Noto Sans Georgian, Menlo, Courier New"); +// Gujarati +pref("font.name-list.serif.x-gujr", "Noto Serif Gujarati, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-gujr", "Noto Sans Gujarati, Helvetica, Arial"); +pref("font.name-list.monospace.x-gujr", "Noto Sans Gujarati, Menlo, Courier New"); +// Gurmukhi +pref("font.name-list.serif.x-guru", "Noto Serif Gurmukhi, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-guru", "Noto Sans Gurmukhi, Helvetica, Arial"); +pref("font.name-list.monospace.x-guru", "Noto Sans Gurmukhi, Menlo, Courier New"); +// Hebrew +pref("font.name-list.serif.he", "Noto Serif Hebrew, Times, Times New Roman"); +pref("font.name-list.sans-serif.he", "Noto Sans Hebrew, Helvetica, Arial"); +pref("font.name-list.monospace.he", "Noto Sans Hebrew, Menlo, Courier New"); +// Kannada +pref("font.name-list.serif.x-knda", "Noto Serif Kannada, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-knda", "Noto Sans Kannada, Helvetica, Arial"); +pref("font.name-list.monospace.x-knda", "Noto Sans Kannada, Menlo, Courier New"); +// Khmer +pref("font.name-list.serif.x-khmr", "Noto Serif Khmer, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-khmr", "Noto Sans Khmer, Helvetica, Arial"); +pref("font.name-list.monospace.x-khmr", "Noto Sans Khmer, Menlo, Courier New"); +// Malayalam +pref("font.name-list.serif.x-mlym", "Noto Serif Malayalam, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-mlym", "Noto Sans Malayalam, Helvetica, Arial"); +pref("font.name-list.monospace.x-mlym", "Noto Sans Malayalam, Menlo, Courier New"); +// Oriya +pref("font.name-list.serif.x-orya", "Noto Sans Oriya, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-orya", "Noto Sans Oriya, Helvetica, Arial"); +pref("font.name-list.monospace.x-orya", "Noto Sans Oriya, Menlo, Courier New"); +// Sinhala +pref("font.name-list.serif.x-sinh", "Noto Serif Sinhala, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-sinh", "Noto Sans Sinhala, Helvetica, Arial"); +pref("font.name-list.monospace.x-sinh", "Noto Sans Sinhala, Menlo, Courier New"); +// Tamil +pref("font.name-list.serif.x-tamil", "Noto Serif Tamil, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-tamil", "Noto Sans Tamil, Helvetica, Arial"); +pref("font.name-list.monospace.x-tamil", "Noto Sans Tamil, Menlo, Courier New"); +// Telugu +pref("font.name-list.serif.x-telu", "Noto Serif Telugu, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-telu", "Noto Sans Telugu, Helvetica, Arial"); +pref("font.name-list.monospace.x-telu", "Noto Sans Telugu, Menlo, Courier New"); +// Tibetan +pref("font.name-list.serif.x-tibt", "Noto Serif Tibetan, Times, Times New Roman"); +pref("font.name-list.sans-serif.x-tibt", "Noto Serif Tibetan, Helvetica, Arial"); +pref("font.name-list.monospace.x-tibt", "Noto Serif Tibetan, Menlo, Courier New"); +// Others (Balinese, Grantha, Khojki, Lao, Myanmar) +pref("font.name-list.serif.x-unicode", "Times, Times New Roman, Noto Serif Balinese, Noto Serif Grantha, Noto Serif Khojki, Noto Serif Lao, Noto Serif Myanmar"); +pref("font.name-list.sans-serif.x-unicode", "Helvetica, Arial, Noto Sans Balinese, Noto Sans Grantha, Noto Sans Khojki, Noto Sans Lao, Noto Sans Myanmar"); +pref("font.name-list.monospace.x-unicode", "Menlo, Courier New, Noto Sans Balinese, Noto Sans Grantha, Noto Sans Khojki, Noto Sans Lao, Noto Sans Myanmar"); +// The rest are not customized, because they are covered only by one font +#endif + +#ifdef XP_WIN +pref("font.system.whitelist", "Arial, Cambria Math, Consolas, Courier New, Georgia, Lucida Console, MS Gothic, MS ゴシック, MS PGothic, MS Pゴシック, MV Boli, Malgun Gothic, Mangal, Microsoft Himalaya, Microsoft JhengHei, Microsoft YaHei, 微软雅黑, MingLiU, 細明體, PMingLiU, 新細明體, Segoe UI, SimSun, 宋体, Sylfaen, Tahoma, Times New Roman, Verdana, Twemoji Mozilla, Noto Sans Adlam, Noto Sans Balinese, Noto Sans Bamum, Noto Sans Bassa Vah, Noto Sans Batak, Noto Sans Bengali, Noto Sans Buginese, Noto Sans Bu [...] + +// Arabic +pref("font.name-list.serif.ar", "Times New Roman, Noto Naskh Arabic"); +pref("font.name-list.sans-serif.ar", "Segoe UI, Tahoma, Arial, Noto Naskh Arabic"); +pref("font.name-list.monospace.ar", "Consolas, Noto Naskh Arabic"); +// Bengali +pref("font.name-list.serif.x-beng", "Noto Serif Bengali, Times New Roman"); +pref("font.name-list.sans-serif.x-beng", "Noto Sans Bengali, Arial"); +pref("font.name-list.monospace.x-beng", "Noto Sans Bengali, Consolas"); +// Canadian Aboriginal +pref("font.name-list.serif.x-cans", "Noto Serif Canadian Aboriginal, Times New Roman"); +pref("font.name-list.sans-serif.x-cans", "Noto Sans Canadian Aboriginal, Arial"); +pref("font.name-list.monospace.x-cans", "Noto Sans Canadian Aboriginal, Consolas"); +// Cyrillic (we use Noto only for fallback, system fonts have a good coverage) +pref("font.name-list.serif.x-cyrillic", "Times New Roman, Noto Serif"); +pref("font.name-list.sans-serif.x-cyrillic", "Arial, Noto Sans"); +// Devanagari +pref("font.name-list.serif.x-devanagari", "Noto Serif Devanagari, Times New Roman"); +pref("font.name-list.sans-serif.x-devanagari", "Noto Sans Devanagari, Arial"); +pref("font.name-list.monospace.x-devanagari", "Noto Sans Devanagari, Consolas"); +// Ethiopic +pref("font.name-list.serif.x-ethi", "Noto Serif Ethiopic, Times New Roman"); +pref("font.name-list.sans-serif.x-ethi", "Noto Sans Ethiopic, Arial"); +pref("font.name-list.monospace.x-ethi", "Noto Sans Ethiopic, Consolas"); +// Georgian +pref("font.name-list.serif.x-geor", "Noto Serif Georgian, Times New Roman"); +pref("font.name-list.sans-serif.x-geor", "Noto Sans Georgian, Arial"); +pref("font.name-list.monospace.x-geor", "Noto Sans Georgian, Consolas"); +// Gujarati +pref("font.name-list.serif.x-gujr", "Noto Serif Gujarati, Times New Roman"); +pref("font.name-list.sans-serif.x-gujr", "Noto Sans Gujarati, Arial"); +pref("font.name-list.monospace.x-gujr", "Noto Sans Gujarati, Consolas"); +// Gurmukhi +pref("font.name-list.serif.x-guru", "Noto Serif Gurmukhi, Times New Roman"); +pref("font.name-list.sans-serif.x-guru", "Noto Sans Gurmukhi, Arial"); +pref("font.name-list.monospace.x-guru", "Noto Sans Gurmukhi, Consolas"); +// Kannada +pref("font.name-list.serif.x-knda", "Noto Serif Kannada, Times New Roman"); +pref("font.name-list.sans-serif.x-knda", "Noto Sans Kannada, Arial"); +pref("font.name-list.monospace.x-knda", "Noto Sans Kannada, Consolas"); +// Khmer +pref("font.name-list.serif.x-khmr", "Noto Serif Khmer, Times New Roman"); +pref("font.name-list.sans-serif.x-khmr", "Noto Sans Khmer, Arial"); +pref("font.name-list.monospace.x-khmr", "Noto Sans Khmer, Consolas"); +// Malayalam +pref("font.name-list.serif.x-mlym", "Noto Serif Malayalam, Times New Roman"); +pref("font.name-list.sans-serif.x-mlym", "Noto Sans Malayalam, Arial"); +pref("font.name-list.monospace.x-mlym", "Noto Sans Malayalam, Consolas"); +// Oriya +pref("font.name-list.serif.x-orya", "Noto Sans Oriya, Times New Roman"); +pref("font.name-list.sans-serif.x-orya", "Noto Sans Oriya, Arial"); +pref("font.name-list.monospace.x-orya", "Noto Sans Oriya, Consolas"); +// Sinhala +pref("font.name-list.serif.x-sinh", "Noto Serif Sinhala, Times New Roman"); +pref("font.name-list.sans-serif.x-sinh", "Noto Sans Sinhala, Arial"); +pref("font.name-list.monospace.x-sinh", "Noto Sans Sinhala, Consolas"); +// Tamil +pref("font.name-list.serif.x-tamil", "Noto Serif Tamil, Times New Roman"); +pref("font.name-list.sans-serif.x-tamil", "Noto Sans Tamil, Arial"); +pref("font.name-list.monospace.x-tamil", "Noto Sans Tamil, Consolas"); +// Telugu +pref("font.name-list.serif.x-telu", "Noto Serif Telugu, Times New Roman"); +pref("font.name-list.sans-serif.x-telu", "Noto Sans Telugu, Arial"); +pref("font.name-list.monospace.x-telu", "Noto Sans Telugu, Consolas"); +// Tibetan +pref("font.name-list.serif.x-tibt", "Microsoft Himalaya, Noto Serif Tibetan, Times New Roman"); +pref("font.name-list.sans-serif.x-tibt", "Microsoft Himalaya, Noto Serif Tibetan, Arial"); +pref("font.name-list.monospace.x-tibt", "Microsoft Himalaya, Noto Serif Tibetan, Consolas"); +// Others (Balinese, Grantha, Khojki, Lao, Myanmar) +pref("font.name-list.serif.x-unicode", "Times New Roman, Noto Serif Balinese, Noto Serif Grantha, Noto Serif Khojki, Noto Serif Lao, Noto Serif Myanmar"); +pref("font.name-list.sans-serif.x-unicode", "Arial, Noto Sans Balinese, Noto Sans Grantha, Noto Sans Khojki, Noto Sans Lao, Noto Sans Myanmar"); +pref("font.name-list.monospace.x-unicode", "Consolas, Noto Sans Balinese, Noto Sans Grantha, Noto Sans Khojki, Noto Sans Lao, Noto Sans Myanmar"); +// The rest are not customized, because they are covered only by one font +#endif + +#ifdef XP_LINUX +pref("layout.css.font-visibility.resistFingerprinting", 3); // work around bug 41163 + +// Arabic +pref("font.name-list.serif.ar", "Noto Naskh Arabic, Tinos"); +pref("font.name-list.sans-serif.ar", "Noto Naskh Arabic, Arimo"); +pref("font.name-list.monospace.ar", "Noto Naskh Arabic, Cousine"); +// Armenian +pref("font.name-list.serif.x-armn", "Noto Serif Armenian, Tinos"); +pref("font.name-list.sans-serif.x-armn", "Noto Sans Armenian, Arimo"); +pref("font.name-list.sans-serif.x-armn", "Noto Sans Armenian, Cousine"); +// Bengali +pref("font.name-list.serif.x-beng", "Noto Serif Bengali, Tinos"); +pref("font.name-list.sans-serif.x-beng", "Noto Sans Bengali, Arimo"); +pref("font.name-list.monospace.x-beng", "Noto Sans Bengali, Cousine"); +// Canadian Aboriginal +pref("font.name-list.serif.x-cans", "Noto Serif Canadian Aboriginal, Tinos"); +pref("font.name-list.sans-serif.x-cans", "Noto Sans Canadian Aboriginal, Arimo"); +pref("font.name-list.monospace.x-cans", "Noto Sans Canadian Aboriginal, Cousine"); +// ChineseCN +pref("font.name-list.serif.zh-CN", "Noto Sans SC Regular, Tinos"); +pref("font.name-list.sans-serif.zh-CN", "Noto Sans SC Regular, Arimo"); +pref("font.name-list.monospace.zh-CN", "Noto Sans SC Regular, Cousine"); +// ChineseHK +pref("font.name-list.serif.zh-HK", "Noto Sans TC Regular, Tinos"); +pref("font.name-list.sans-serif.zh-HK", "Noto Sans TC Regular, Arimo"); +pref("font.name-list.monospace.zh-HK", "Noto Sans TC Regular, Cousine"); +// ChineseTW +pref("font.name-list.serif.zh-TW", "Noto Sans TC Regular, Tinos"); +pref("font.name-list.sans-serif.zh-TW", "Noto Sans TC Regular, Arimo"); +pref("font.name-list.monospace.zh-TW", "Noto Sans TC Regular, Cousine"); +// Cyrillic +pref("font.name-list.serif.x-cyrillic", "Tinos"); +pref("font.name-list.sans-serif.x-cyrillic", "Arimo"); +pref("font.name-list.monospace.x-cyrillic", "Cousine"); +// Devanagari +pref("font.name-list.serif.x-devanagari", "Noto Serif Devanagari, Tinos"); +pref("font.name-list.sans-serif.x-devanagari", "Noto Sans Devanagari, Arimo"); +pref("font.name-list.monospace.x-devanagari", "Noto Sans Devanagari, Cousine"); +// Ethiopic +pref("font.name-list.serif.x-ethi", "Noto Serif Ethiopic, Tinos"); +pref("font.name-list.sans-serif.x-ethi", "Noto Sans Ethiopic, Arimo"); +pref("font.name-list.monospace.x-ethi", "Noto Sans Ethiopic, Cousine"); +// Georgian +pref("font.name-list.serif.x-geor", "Noto Serif Georgian, Tinos"); +pref("font.name-list.sans-serif.x-geor", "Noto Sans Georgian, Arimo"); +pref("font.name-list.monospace.x-geor", "Noto Sans Georgian, Cousine"); +// Greek +pref("font.name-list.serif.el", "Tinos"); +pref("font.name-list.sans-serif.el", "Arimo"); +pref("font.name-list.monospace.el", "Cousine"); +// Gujarati +pref("font.name-list.serif.x-gujr", "Noto Serif Gujarati, Tinos"); +pref("font.name-list.sans-serif.x-gujr", "Noto Sans Gujarati, Arimo"); +pref("font.name-list.monospace.x-gujr", "Noto Sans Gujarati, Cousine"); +// Gurmukhi +pref("font.name-list.serif.x-guru", "Noto Serif Gurmukhi, Tinos"); +pref("font.name-list.sans-serif.x-guru", "Noto Sans Gurmukhi, Arimo"); +pref("font.name-list.monospace.x-guru", "Noto Sans Gurmukhi, Cousine"); +// Hebrew +pref("font.name-list.serif.he", "Noto Serif Hebrew, Tinos"); +pref("font.name-list.sans-serif.he", "Noto Sans Hebrew, Arimo"); +pref("font.name-list.monospace.he", "Noto Sans Hebrew, Cousine"); +// Japanese +pref("font.name-list.serif.ja", "Noto Sans JP Regular, Tinos"); +pref("font.name-list.sans-serif.ja", "Noto Sans JP Regular, Arimo"); +pref("font.name-list.monospace.ja", "Noto Sans JP Regular, Cousine"); +// Kannada +pref("font.name-list.serif.x-knda", "Noto Serif Kannada, Tinos"); +pref("font.name-list.sans-serif.x-knda", "Noto Sans Kannada, Arimo"); +pref("font.name-list.monospace.x-knda", "Noto Sans Kannada, Cousine"); +// Khmer +pref("font.name-list.serif.x-khmr", "Noto Serif Khmer, Tinos"); +pref("font.name-list.sans-serif.x-khmr", "Noto Sans Khmer, Arimo"); +pref("font.name-list.monospace.x-khmr", "Noto Sans Khmer, Cousine"); +// Korean +pref("font.name-list.serif.ko", "Noto Sans KR Regular"); +pref("font.name-list.sans-serif.ko", "Noto Sans KR Regular"); +pref("font.name-list.monospace.ko", "Noto Sans KR Regular"); +// Malayalam +pref("font.name-list.serif.x-mlym", "Noto Serif Malayalam, Tinos"); +pref("font.name-list.sans-serif.x-mlym", "Noto Sans Malayalam, Arimo"); +pref("font.name-list.monospace.x-mlym", "Noto Sans Malayalam, Cousine"); +// Mathematics +pref("font.name-list.serif.x-math", "STIX Math Regular, Tinos"); +pref("font.name-list.sans-serif.x-math", "STIX Math Regular, Arimo"); +pref("font.name-list.monospace.x-math", "STIX Math Regular, Cousine"); +// Oriya +pref("font.name-list.serif.x-orya", "Noto Sans Oriya, Tinos"); +pref("font.name-list.sans-serif.x-orya", "Noto Sans Oriya, Arimo"); +pref("font.name-list.monospace.x-orya", "Noto Sans Oriya, Cousine"); +// Sinhala +pref("font.name-list.serif.x-sinh", "Noto Serif Sinhala, Tinos"); +pref("font.name-list.sans-serif.x-sinh", "Noto Sans Sinhala, Arimo"); +pref("font.name-list.monospace.x-sinh", "Noto Sans Sinhala, Cousine"); +// Tamil +pref("font.name-list.serif.x-tamil", "Noto Serif Tamil, Tinos"); +pref("font.name-list.sans-serif.x-tamil", "Noto Sans Tamil, Arimo"); +pref("font.name-list.monospace.x-tamil", "Noto Sans Tamil, Cousine"); +// Telugu +pref("font.name-list.serif.x-telu", "Noto Serif Telugu, Tinos"); +pref("font.name-list.sans-serif.x-telu", "Noto Sans Telugu, Arimo"); +pref("font.name-list.monospace.x-telu", "Noto Sans Telugu, Cousine"); +// Thai +pref("font.name-list.serif.th", "Noto Serif Thai, Tinos"); +pref("font.name-list.sans-serif.th", "Noto Sans Thai, Arimo"); +pref("font.name-list.monospace.th", "Noto Sans Thai, Cousine"); +// Tibetan +pref("font.name-list.serif.x-tibt", "Noto Serif Tibetan, Tinos"); +pref("font.name-list.sans-serif.x-tibt", "Noto Serif Tibetan, Arimo"); +pref("font.name-list.monospace.x-tibt", "Noto Serif Tibetan, Cousine"); +// Western +pref("font.name-list.serif.x-western", "Tinos"); +pref("font.name-list.sans-serif.x-western", "Arimo"); +pref("font.name-list.monospace.x-western", "Cousine"); +// Others (Balinese, Grantha, Khojki, Lao, Myanmar) +pref("font.name-list.serif.x-unicode", "Tinos, Noto Serif Balinese, Noto Serif Grantha, Noto Serif Khojki, Noto Serif Lao, Noto Serif Myanmar"); +pref("font.name-list.sans-serif.x-unicode", "Arimo, Noto Sans Balinese, Noto Sans Grantha, Noto Sans Khojki, Noto Sans Lao, Noto Sans Myanmar"); +pref("font.name-list.monospace.x-unicode", "Cousine, Noto Sans Balinese, Noto Sans Grantha, Noto Sans Khojki, Noto Sans Lao, Noto Sans Myanmar"); +// The rest are not customized, because they are covered only by one font +#endif +#endif diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 84c76fd5eeff..16060fa72eb0 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -50,9 +50,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 c604afd2cbf8..bdc712aaa8e7 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -275,6 +275,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 7b5566ac5de7..0df0a824f9ad 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/mobile/android/app/geckoview-prefs.js b/mobile/android/app/geckoview-prefs.js index 35092e25a647..6b1fc4e55cc9 100644 --- a/mobile/android/app/geckoview-prefs.js +++ b/mobile/android/app/geckoview-prefs.js @@ -93,3 +93,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 9f5e09929199..d19ef30c4442 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -355,7 +355,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 21fa8617c5ff..4686e3df08b8 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 246359bc4ba1..d354c81e71d3 100644 --- a/taskcluster/ci/source-test/mozlint.yml +++ b/taskcluster/ci/source-test/mozlint.yml @@ -151,6 +151,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit cf65343370a8c68ace94db44427c02ff9ff47267 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Wed Jul 6 22:06:01 2022 +0200
Bug 41043: Hardcode the UI font on Linux
The mechanism to choose the UI font does not play well with our fontconfig configuration. As a result, the final criterion to choose the font for the UI was its version.
Since we hardcode Arimo as a default sans-serif on preferences, we use it also for the UI. FontConfig will fall back to some other font for scripts Arimo does not cover as expected (we tested with Japanese). --- gfx/thebes/gfxFcPlatformFontList.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp index ddd230be7e46..69286ae6dbe4 100644 --- a/gfx/thebes/gfxFcPlatformFontList.cpp +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -2033,10 +2033,12 @@ void gfxFcPlatformFontList::GetFontList(nsAtom* aLangGroup, FontFamily gfxFcPlatformFontList::GetDefaultFontForPlatform( nsPresContext* aPresContext, const gfxFontStyle* aStyle, nsAtom* aLanguage) { - // Get the default font by using a fake name to retrieve the first - // scalable font that fontconfig suggests for the given language. + // We hardcode Arimo also in preferences, and using the original code that + // tried to resolve a non-existing font did not play well with our fontconfig + // configuration. See + // https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41043 PrefFontList* prefFonts = - FindGenericFamilies(aPresContext, "-moz-default"_ns, + FindGenericFamilies(aPresContext, "Arimo"_ns, aLanguage ? aLanguage : nsGkAtoms::x_western); NS_ASSERTION(prefFonts, "null list of generic fonts"); if (prefFonts && !prefFonts->IsEmpty()) {
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 32864a3269a2eb6f4f46a20a2a2d130b85d9f64e 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.
Bug 40198: Expose privacy.spoof_english pref in GeckoView --- mobile/android/components/geckoview/GeckoViewStartup.jsm | 5 +++++ mobile/android/geckoview/api.txt | 3 +++ .../main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java | 11 +++++++++++ 3 files changed, 19 insertions(+)
diff --git a/mobile/android/components/geckoview/GeckoViewStartup.jsm b/mobile/android/components/geckoview/GeckoViewStartup.jsm index c5205535c2bf..fd61e358a9b8 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"); @@ -253,6 +254,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 ); diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 8e920543675a..4ec5b10c4da8 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -810,6 +810,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 boolean getUseMaxScreenDepth(); method public boolean getWebFontsEnabled(); @@ -831,6 +832,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 setWebFontsEnabled(boolean); method @NonNull public GeckoRuntimeSettings setWebManifestEnabled(boolean); field public static final int ALLOW_ALL = 0; @@ -870,6 +872,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 useMaxScreenDepth(boolean); method @NonNull public GeckoRuntimeSettings.Builder webFontsEnabled(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 305f6a03bff4..4aa10d3b4936 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 @@ -453,6 +453,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { getSettings().setAllowInsecureConnections(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;
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit a4fb3f44f3b63b351ec04804b6266e4c68d184ad 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 4aa10d3b4936..b96fbd15cf5d 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 @@ -791,19 +791,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 1890295396850edb124183e8f1466591d538b494 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 | 9 +++++++++ 2 files changed, 27 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 6d4f37ebf371..a2e43def0b4d 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 @@ -40,6 +40,11 @@ public class WebRequest extends WebMessage { */ public final @CacheMode int cacheMode;
+ /** + * The value of the origin of this request. + */ + public final @Nullable String origin; + /** * If true, do not use newer protocol features that might have interop problems on the Internet. * Intended only for use with critical infrastructure. @@ -110,6 +115,7 @@ public class WebRequest extends WebMessage { cacheMode = builder.mCacheMode; referrer = builder.mReferrer; beConservative = builder.mBeConservative; + origin = builder.mOrigin;
if (builder.mBody != null) { body = builder.mBody.asReadOnlyBuffer(); @@ -125,6 +131,7 @@ public class WebRequest extends WebMessage { /* package */ int mCacheMode = CACHE_MODE_DEFAULT; /* package */ String mReferrer; /* package */ boolean mBeConservative; + /* package */ String mOrigin;
/** * Construct a Builder instance with the specified URI. @@ -235,6 +242,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. */ public @NonNull WebRequest build() { if (mUri == null) { diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp index 21cda516da08..b48d67608871 100644 --- a/widget/android/WebExecutorSupport.cpp +++ b/widget/android/WebExecutorSupport.cpp @@ -395,6 +395,15 @@ nsresult WebExecutorSupport::CreateStreamLoader( MOZ_ASSERT(cookieJarSettings);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + + if (const auto origin = req->Origin(); origin) { + RefPtr<nsIURI> originUri; + 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 0a3ce2b7142d5a320a31e57921dfc24b45287209 Author: Alex Catarineu acat@torproject.org AuthorDate: Tue Sep 10 16:29:31 2019 +0200
Bug 26345: Hide tracking protection UI --- browser/base/content/browser-siteIdentity.js | 4 ++-- browser/components/about/AboutRedirector.cpp | 5 ----- browser/components/about/components.conf | 1 - browser/components/moz.build | 1 - browser/themes/shared/preferences/privacy.css | 4 ++++ 5 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 81458c6a6e60..02fdb494d290 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -898,10 +898,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 3c4c1a6615cc..7c915f2b45b0 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -133,11 +133,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 | - nsIAboutModule::IS_SECURE_CHROME_UI}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index acc275697863..56226324bc32 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -21,7 +21,6 @@ pages = [ 'policies', 'preferences', 'privatebrowsing', - 'protections', 'profiling', 'reader', 'restartrequired', diff --git a/browser/components/moz.build b/browser/components/moz.build index b4e2a0f367b0..0f9378a2f147 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -45,7 +45,6 @@ DIRS += [ "preferences", "privatebrowsing", "prompts", - "protections", "protocolhandler", "resistfingerprinting", "screenshots", diff --git a/browser/themes/shared/preferences/privacy.css b/browser/themes/shared/preferences/privacy.css index 30927d313730..f59d8a79c07d 100644 --- a/browser/themes/shared/preferences/privacy.css +++ b/browser/themes/shared/preferences/privacy.css @@ -65,6 +65,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 70a342858d62156522359f6a0a7734b3f7e95ecb 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 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 1896b995a8d4..3281543dc71a 100644 --- a/browser/config/mozconfigs/base-browser +++ b/browser/config/mozconfigs/base-browser @@ -32,3 +32,5 @@ 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/moz.configure b/moz.configure index 8b74afbabec1..11d93c563a5d 100755 --- a/moz.configure +++ b/moz.configure @@ -1017,6 +1017,25 @@ set_config("ZLIB_IN_MOZGLUE", zlib_in_mozglue) set_define("ZLIB_IN_MOZGLUE", zlib_in_mozglue)
+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 79baf8416083..fca0b5b314d1 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2777,6 +2777,8 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) { #endif // MOZ_WIDGET_ANDROID }
+// 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, @@ -2784,17 +2786,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 5536f4423f4a..0454863ac07a 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -234,9 +234,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)) { @@ -252,9 +249,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)) { @@ -1328,6 +1322,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) { @@ -1337,7 +1332,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) { @@ -1480,6 +1489,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);
@@ -1531,13 +1559,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; @@ -1545,23 +1568,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);
@@ -1573,11 +1585,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(); @@ -1585,30 +1592,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 40cd9c8eb06a..a9d017d18e0f 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 ef974f99048f..60b1775eda97 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 0b1c08d152528ed870366358d81d7cd3a8855d5f 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 86be956189c8..c8916988f251 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 07c909ae61cf4c59bcd55c50a6a21a68853a5f5f 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 bc1f35e5b79c..7d8bc93933bf 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -480,7 +480,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; }
@@ -493,7 +493,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; } } @@ -1462,6 +1465,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 6c13f97405147a08d455778413f1aac8d8e8bb98 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 cea8456606bf..76622614a80a 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 e8698376b5be..f34e102721d2 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 6c1ed314b3b620d05e001c0aec4058204720611a 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 fca0b5b314d1..b98668607c9b 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2210,8 +2210,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 @@ -4384,16 +4386,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit feed12a52c31345003caed41f3dc453cb8b211f0 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 7bc03d10bd2a..befa9c8b1def 100644 --- a/layout/generic/ReflowInput.cpp +++ b/layout/generic/ReflowInput.cpp @@ -34,6 +34,7 @@ #include "nsTableCellFrame.h" #include "nsTableFrame.h" #include "StickyScrollContainer.h" +#include "nsContentUtils.h"
using namespace mozilla; using namespace mozilla::css; @@ -2715,7 +2716,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; @@ -2723,6 +2725,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; @@ -2740,7 +2748,8 @@ static nscoord GetNormalLineHeight(nsFontMetrics* aFontMetrics) { return normalLineHeight; }
-static inline nscoord ComputeLineHeight(const ComputedStyle* aComputedStyle, +static inline nscoord ComputeLineHeight(nsIContent* aContent, + const ComputedStyle* aComputedStyle, nsPresContext* aPresContext, nscoord aBlockBSize, float aFontSizeInflation) { @@ -2769,7 +2778,7 @@ static inline nscoord ComputeLineHeight(const ComputedStyle* aComputedStyle,
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle( aComputedStyle, aPresContext, aFontSizeInflation); - return GetNormalLineHeight(fm); + return GetNormalLineHeight(aContent, fm); }
nscoord ReflowInput::GetLineHeight() const { @@ -2806,7 +2815,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"); @@ -2819,7 +2828,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 448f8bab86ef..54926eb0c155 100644 --- a/layout/generic/test/mochitest.ini +++ b/layout/generic/test/mochitest.ini @@ -150,3 +150,4 @@ support-files = file_reframe_for_lazy_load_image.html [test_bug1655135.html] [test_bug1756831.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 000000000000..8ff1d2190c45 --- /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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 20484f4dd459193ade56d451490738dec35404ff 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 4ff18a1a90c4..542eed3e37fd 100644 --- a/intl/locale/LocaleService.cpp +++ b/intl/locale/LocaleService.cpp @@ -14,6 +14,7 @@ #include "mozilla/intl/AppDateTimeFormat.h" #include "mozilla/intl/Locale.h" #include "mozilla/intl/OSPreferences.h" +#include "nsContentUtils.h" #include "nsDirectoryService.h" #include "nsDirectoryServiceDefs.h" #include "nsIObserverService.h" @@ -479,6 +480,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 33fe51f2a80af7db8c5bbe2c37ecbde10a886418 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 0ec02e346617..c86b006a6b4c 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -866,8 +866,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)); @@ -910,11 +935,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit e97df7087e3f257b04a987395570bc62721a69ee 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.css | 6 ++ .../components/resistfingerprinting/RFPHelper.jsm | 97 +++++++++++++++++++--- 4 files changed, 107 insertions(+), 12 deletions(-)
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 03a778809dc8..2f181244982d 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -98,6 +98,13 @@ body { -moz-window-dragging: drag; }
+.browserStack > browser.letterboxing { + border-color: var(--chrome-content-separator-color); + border-style: solid; + border-width : 1px; + border-top: none; +} + #toolbar-menubar[autohide="true"] { overflow: hidden; } diff --git a/browser/base/content/tabbrowser-tab.js b/browser/base/content/tabbrowser-tab.js index abcabb98503e..4243079a84b9 100644 --- a/browser/base/content/tabbrowser-tab.js +++ b/browser/base/content/tabbrowser-tab.js @@ -239,6 +239,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.css b/browser/themes/shared/tabs.css index 2eb7405c78d5..9597205f7606 100644 --- a/browser/themes/shared/tabs.css +++ b/browser/themes/shared/tabs.css @@ -56,6 +56,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 d2191ee14aa5..146e4a86514e 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() { @@ -352,6 +353,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);
@@ -360,9 +379,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); @@ -530,10 +553,32 @@ 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( @@ -554,19 +599,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 = ""; }); } @@ -584,6 +645,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); } @@ -607,10 +673,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 968f12feb41d564e6c705ea408ff547701d783f8 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 84a87c7489d9..1fe5bab0f22e 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", @@ -340,6 +342,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); @@ -2139,6 +2145,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, @@ -2151,6 +2196,8 @@ var ExtensionParent = { watchExtensionProxyContextLoad, watchExtensionWorkerContextLoaded, DebugUtils, + torSendExtensionMessage, + torWaitForExtensionMessage, };
// browserPaintedPromise and browserStartupPromise are promises that
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit f9b149daef54b85962246a4ab49d1dc6858dc1f1 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 499f5ac484e8..0c004765d27f 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -2941,6 +2941,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 8e758c6e952c41c7238078f4482935cbd30a8fd8 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Fri Jul 8 16:19:41 2022 +0200
Bug 40925: Implemented the Security Level component
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.
Bug 40125: Expose Security Level pref in GeckoView --- browser/app/profile/001-base-profile.js | 2 +- 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 + browser/components/securitylevel/SecurityLevel.jsm | 421 ++++++++++++++ .../securitylevel/SecurityLevel.manifest | 1 + browser/components/securitylevel/components.conf | 10 + .../securitylevel/content/securityLevel.js | 611 +++++++++++++++++++++ .../securitylevel/content/securityLevelButton.css | 18 + .../content/securityLevelButton.inc.xhtml | 7 + .../securitylevel/content/securityLevelIcon.svg | 40 ++ .../securitylevel/content/securityLevelPanel.css | 71 +++ .../content/securityLevelPanel.inc.xhtml | 44 ++ .../content/securityLevelPreferences.css | 51 ++ .../content/securityLevelPreferences.inc.xhtml | 62 +++ browser/components/securitylevel/jar.mn | 11 + .../locale/en-US/securityLevel.properties | 30 + browser/components/securitylevel/moz.build | 13 + browser/installer/package-manifest.in | 5 + .../shared/customizableui/panelUI-shared.css | 3 +- mobile/android/geckoview/api.txt | 3 + .../mozilla/geckoview/GeckoRuntimeSettings.java | 11 + 27 files changed, 1451 insertions(+), 2 deletions(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index 7ba669c37aa7..1858d29c98c1 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -324,7 +324,7 @@ pref("extensions.webextensions.restrictedDomains", ""); 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","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","new-window-button","save-page-button","print-bu [...] +pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-list":[],"PersonalToolbar":["personal-bookmarks"],"nav-bar":["back-button","forward-button","stop-reload-button","urlbar-container","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-window-button","sav [...]
// Enforce certificate pinning, see: https://bugs.torproject.org/16206 pref("security.cert_pinning.enforcement_level", 2); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 628c58cc2208..ac8b1d100ad8 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -223,6 +223,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", @@ -1772,6 +1777,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 @@ -2492,6 +2500,8 @@ var gBrowserInit = {
DownloadsButton.uninit();
+ SecurityLevelButton.uninit(); + gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) { diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 32fca7fdc89c..5f53fa119435 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -20,6 +20,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 391bf512c7e7..f44d1635165e 100644 --- a/browser/base/content/main-popupset.inc.xhtml +++ b/browser/base/content/main-popupset.inc.xhtml @@ -534,6 +534,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 316d3ef98371..4e216ac82508 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -405,6 +405,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 0f9378a2f147..09c7d2a3767e 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -49,6 +49,7 @@ DIRS += [ "resistfingerprinting", "screenshots", "search", + "securitylevel", "sessionstore", "shell", "syncedtabs", diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 6ee14eec9b2e..8706870466fa 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"?>
<!DOCTYPE html>
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index a6733ca978bc..8b2a1c99390d 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -1038,6 +1038,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 e86645f30551..b1413b208521 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -48,6 +48,13 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() { } });
+// TODO: module import via ChromeUtils.defineModuleGetter +XPCOMUtils.defineLazyScriptGetter( + this, + ["SecurityLevelPreferences"], + "chrome://browser/content/securitylevel/securityLevel.js" +); + XPCOMUtils.defineLazyPreferenceGetter( this, "OS_AUTH_ENABLED", @@ -314,6 +321,18 @@ function initTCPRolloutSection() { 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. */ @@ -509,6 +528,7 @@ var gPrivacyPane = { this.trackingProtectionReadPrefs(); this.networkCookieBehaviorReadPrefs(); this._initTrackingProtectionExtensionControl(); + this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/components/securitylevel/SecurityLevel.jsm b/browser/components/securitylevel/SecurityLevel.jsm new file mode 100644 index 000000000000..60e95d777192 --- /dev/null +++ b/browser/components/securitylevel/SecurityLevel.jsm @@ -0,0 +1,421 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["SecurityLevel"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const BrowserTopics = Object.freeze({ + ProfileAfterChange: "profile-after-change", +}); + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + ExtensionParent: "resource://gre/modules/ExtensionParent.jsm", +}); + +// Logger adapted from CustomizableUI.jsm +XPCOMUtils.defineLazyGetter(this, "logger", () => { + const { ConsoleAPI } = ChromeUtils.import( + "resource://gre/modules/Console.jsm" + ); + let consoleOptions = { + maxLogLevel: "info", + prefix: "SecurityLevel", + }; + return new ConsoleAPI(consoleOptions); +}); + +// The Security Settings prefs in question. +const kSliderPref = "extensions.torbutton.security_slider"; +const kCustomPref = "extensions.torbutton.security_custom"; +const kSliderMigration = "extensions.torbutton.security_slider_migration"; + +// __getPrefValue(prefName)__ +// Returns the current value of a preference, regardless of its type. +var getPrefValue = function(prefName) { + switch (Services.prefs.getPrefType(prefName)) { + case Services.prefs.PREF_BOOL: + return Services.prefs.getBoolPref(prefName); + case Services.prefs.PREF_INT: + return Services.prefs.getIntPref(prefName); + case Services.prefs.PREF_STRING: + return Services.prefs.getCharPref(prefName); + default: + return null; + } +}; + +// __bindPref(prefName, prefHandler, init)__ +// Applies prefHandler whenever the value of the pref changes. +// If init is true, applies prefHandler to the current value. +// Returns a zero-arg function that unbinds the pref. +var bindPref = function(prefName, prefHandler, init = false) { + let update = () => { + prefHandler(getPrefValue(prefName)); + }, + observer = { + observe(subject, topic, data) { + if (data === prefName) { + update(); + } + }, + }; + Services.prefs.addObserver(prefName, observer); + if (init) { + update(); + } + return () => { + Services.prefs.removeObserver(prefName, observer); + }; +}; + +// __bindPrefAndInit(prefName, prefHandler)__ +// Applies prefHandler to the current value of pref specified by prefName. +// Re-applies prefHandler whenever the value of the pref changes. +// Returns a zero-arg function that unbinds the pref. +var bindPrefAndInit = (prefName, prefHandler) => + bindPref(prefName, prefHandler, true); + +async function waitForExtensionMessage(extensionId, checker = () => {}) { + const { torWaitForExtensionMessage } = ExtensionParent; + if (torWaitForExtensionMessage) { + return torWaitForExtensionMessage(extensionId, checker); + } + return undefined; +} + +async function sendExtensionMessage(extensionId, message) { + const { torSendExtensionMessage } = ExtensionParent; + if (torSendExtensionMessage) { + return torSendExtensionMessage(extensionId, message); + } + return undefined; +} + +// ## NoScript settings + +// Minimum and maximum capability states as controlled by NoScript. +const max_caps = [ + "fetch", + "font", + "frame", + "media", + "object", + "other", + "script", + "webgl", + "noscript", +]; +const min_caps = ["frame", "other", "noscript"]; + +// Untrusted capabilities for [Standard, Safer, Safest] safety levels. +const untrusted_caps = [ + max_caps, // standard safety: neither http nor https + ["frame", "font", "object", "other", "noscript"], // safer: http + min_caps, // safest: neither http nor https +]; + +// Default capabilities for [Standard, Safer, Safest] safety levels. +const default_caps = [ + max_caps, // standard: both http and https + ["fetch", "font", "frame", "object", "other", "script", "noscript"], // safer: https only + min_caps, // safest: both http and https +]; + +// __noscriptSettings(safetyLevel)__. +// Produces NoScript settings with policy according to +// the safetyLevel which can be: +// 0 = Standard, 1 = Safer, 2 = Safest +// +// At the "Standard" safety level, we leave all sites at +// default with maximal capabilities. Essentially no content +// is blocked. +// +// At "Safer", we set all http sites to untrusted, +// and all https sites to default. Scripts are only permitted +// on https sites. Neither type of site is supposed to allow +// media, but both allow fonts (as we used in legacy NoScript). +// +// At "Safest", all sites are at default with minimal +// capabilities. Most things are blocked. +let noscriptSettings = safetyLevel => ({ + __meta: { + name: "updateSettings", + recipientInfo: null, + }, + policy: { + DEFAULT: { + capabilities: default_caps[safetyLevel], + temp: false, + }, + TRUSTED: { + capabilities: max_caps, + temp: false, + }, + UNTRUSTED: { + capabilities: untrusted_caps[safetyLevel], + temp: false, + }, + sites: { + trusted: [], + untrusted: [[], ["http:"], []][safetyLevel], + custom: {}, + temp: [], + }, + enforced: true, + autoAllowTop: false, + }, + isTorBrowser: true, + tabId: -1, +}); + +// ## Communications + +// The extension ID for NoScript (WebExtension) +const noscriptID = "{73a6fe31-595d-460b-a920-fcc0f8843232}"; + +// Ensure binding only occurs once. +let initialized = false; + +// __initialize()__. +// The main function that binds the NoScript settings to the security +// slider pref state. +var initializeNoScriptControl = () => { + if (initialized) { + return; + } + initialized = true; + + try { + // LegacyExtensionContext is not there anymore. Using raw + // Services.cpmm.sendAsyncMessage mechanism to communicate with + // NoScript. + + // The component that handles WebExtensions' sendMessage. + + // __setNoScriptSettings(settings)__. + // NoScript listens for internal settings with onMessage. We can send + // a new settings JSON object according to NoScript's + // protocol and these are accepted! See the use of + // `browser.runtime.onMessage.addListener(...)` in NoScript's bg/main.js. + + // TODO: Is there a better way? + let sendNoScriptSettings = settings => + sendExtensionMessage(noscriptID, settings); + + // __setNoScriptSafetyLevel(safetyLevel)__. + // Set NoScript settings according to a particular safety level + // (security slider level): 0 = Standard, 1 = Safer, 2 = Safest + let setNoScriptSafetyLevel = safetyLevel => + sendNoScriptSettings(noscriptSettings(safetyLevel)); + + // __securitySliderToSafetyLevel(sliderState)__. + // Converts the "extensions.torbutton.security_slider" pref value + // to a "safety level" value: 0 = Standard, 1 = Safer, 2 = Safest + let securitySliderToSafetyLevel = sliderState => + [undefined, 2, 1, 1, 0][sliderState]; + + // Wait for the first message from NoScript to arrive, and then + // bind the security_slider pref to the NoScript settings. + let messageListener = a => { + try { + logger.debug("Message received from NoScript:", a); + let noscriptPersist = Services.prefs.getBoolPref( + "extensions.torbutton.noscript_persist", + false + ); + let noscriptInited = Services.prefs.getBoolPref( + "extensions.torbutton.noscript_inited", + false + ); + // Set the noscript safety level once if we have never run noscript + // before, or if we are not allowing noscript per-site settings to be + // persisted between browser sessions. Otherwise make sure that the + // security slider position, if changed, will rewrite the noscript + // settings. + bindPref( + kSliderPref, + sliderState => + setNoScriptSafetyLevel(securitySliderToSafetyLevel(sliderState)), + !noscriptPersist || !noscriptInited + ); + if (!noscriptInited) { + Services.prefs.setBoolPref( + "extensions.torbutton.noscript_inited", + true + ); + } + } catch (e) { + logger.exception(e); + } + }; + waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then( + messageListener + ); + logger.info("Listening for messages from NoScript."); + } catch (e) { + logger.exception(e); + } +}; + +// ### Constants + +// __kSecuritySettings__. +// A table of all prefs bound to the security slider, and the value +// for each security setting. Note that 2-m and 3-m are identical, +// corresponding to the old 2-medium-high setting. We also separately +// bind NoScript settings to the extensions.torbutton.security_slider +// (see noscript-control.js). +/* eslint-disable */ +const kSecuritySettings = { + // Preference name : [0, 1-high 2-m 3-m 4-low] + "javascript.options.ion" : [, false, false, false, true ], + "javascript.options.baselinejit" : [, false, false, false, true ], + "javascript.options.native_regexp" : [, false, false, false, true ], + "mathml.disabled" : [, true, true, true, false], + "gfx.font_rendering.graphite.enabled" : [, false, false, false, true ], + "gfx.font_rendering.opentype_svg.enabled" : [, false, false, false, true ], + "svg.disabled" : [, true, false, false, false], + "javascript.options.asmjs" : [, false, false, false, true ], + "javascript.options.wasm" : [, false, false, false, true ], + "dom.security.https_only_mode_send_http_background_request" : [, false, false, false, true ], +}; +/* eslint-enable */ + +// ### Prefs + +// __write_setting_to_prefs(settingIndex)__. +// Take a given setting index and write the appropriate pref values +// to the pref database. +var write_setting_to_prefs = function(settingIndex) { + Object.keys(kSecuritySettings).forEach(prefName => + Services.prefs.setBoolPref( + prefName, + kSecuritySettings[prefName][settingIndex] + ) + ); +}; + +// __read_setting_from_prefs()__. +// Read the current pref values, and decide if any of our +// security settings matches. Otherwise return null. +var read_setting_from_prefs = function(prefNames) { + prefNames = prefNames || Object.keys(kSecuritySettings); + for (let settingIndex of [1, 2, 3, 4]) { + let possibleSetting = true; + // For the given settingIndex, check if all current pref values + // match the setting. + for (let prefName of prefNames) { + if ( + kSecuritySettings[prefName][settingIndex] !== + Services.prefs.getBoolPref(prefName) + ) { + possibleSetting = false; + } + } + if (possibleSetting) { + // We have a match! + return settingIndex; + } + } + // No matching setting; return null. + return null; +}; + +// __watch_security_prefs(onSettingChanged)__. +// Whenever a pref bound to the security slider changes, onSettingChanged +// is called with the new security setting value (1,2,3,4 or null). +// Returns a zero-arg function that ends this binding. +var watch_security_prefs = function(onSettingChanged) { + let prefNames = Object.keys(kSecuritySettings); + let unbindFuncs = []; + for (let prefName of prefNames) { + unbindFuncs.push( + bindPrefAndInit(prefName, () => + onSettingChanged(read_setting_from_prefs()) + ) + ); + } + // Call all the unbind functions. + return () => unbindFuncs.forEach(unbind => unbind()); +}; + +// __initialized__. +// Have we called initialize() yet? +var initializedSecPrefs = false; + +// __initialize()__. +// Defines the behavior of "extensions.torbutton.security_custom", +// "extensions.torbutton.security_slider", and the security-sensitive +// prefs declared in kSecuritySettings. +var initializeSecurityPrefs = function() { + // Only run once. + if (initializedSecPrefs) { + return; + } + logger.info("Initializing security-prefs.js"); + initializedSecPrefs = true; + // When security_custom is set to false, apply security_slider setting + // to the security-sensitive prefs. + bindPrefAndInit(kCustomPref, function(custom) { + if (custom === false) { + write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref)); + } + }); + // If security_slider is given a new value, then security_custom should + // be set to false. + bindPref(kSliderPref, function(prefIndex) { + Services.prefs.setBoolPref(kCustomPref, false); + write_setting_to_prefs(prefIndex); + }); + // If a security-sensitive pref changes, then decide if the set of pref values + // constitutes a security_slider setting or a custom value. + watch_security_prefs(settingIndex => { + if (settingIndex === null) { + Services.prefs.setBoolPref(kCustomPref, true); + } else { + Services.prefs.setIntPref(kSliderPref, settingIndex); + Services.prefs.setBoolPref(kCustomPref, false); + } + }); + // Migrate from old medium-low (3) to new medium (2). + if ( + Services.prefs.getBoolPref(kCustomPref) === false && + Services.prefs.getIntPref(kSliderPref) === 3 + ) { + Services.prefs.setIntPref(kSliderPref, 2); + write_setting_to_prefs(2); + } + + // Revert #33613 fix + if (Services.prefs.getIntPref(kSliderMigration, 0) < 2) { + // We can't differentiate between users having flipped `javascript.enabled` + // to `false` before it got governed by the security settings vs. those who + // had it flipped due to #33613. Reset the preference for everyone. + if (Services.prefs.getIntPref(kSliderPref) === 1) { + Services.prefs.setBoolPref("javascript.enabled", true); + } + Services.prefs.clearUserPref("media.webaudio.enabled"); + Services.prefs.setIntPref(kSliderMigration, 2); + } + logger.info("security-prefs.js initialization complete"); +}; + +// This class is used to initialize the security level stuff at the startup +class SecurityLevel { + QueryInterface = ChromeUtils.generateQI(["nsIObserver"]); + + init() { + initializeNoScriptControl(); + initializeSecurityPrefs(); + } + + observe(aSubject, aTopic, aData) { + if (aTopic === BrowserTopics.ProfileAfterChange) { + this.init(); + } + } +} diff --git a/browser/components/securitylevel/SecurityLevel.manifest b/browser/components/securitylevel/SecurityLevel.manifest new file mode 100644 index 000000000000..0bfbd2ba1ac7 --- /dev/null +++ b/browser/components/securitylevel/SecurityLevel.manifest @@ -0,0 +1 @@ +category profile-after-change SecurityLevel @torproject.org/security-level;1 diff --git a/browser/components/securitylevel/components.conf b/browser/components/securitylevel/components.conf new file mode 100644 index 000000000000..4b9263ce7f0f --- /dev/null +++ b/browser/components/securitylevel/components.conf @@ -0,0 +1,10 @@ +Classes = [ + { + "cid": "{c602ffe5-abf4-40d0-a944-26738b81efdb}", + "contract_ids": [ + "@torproject.org/security-level;1", + ], + "jsm": "resource:///modules/SecurityLevel.jsm", + "constructor": "SecurityLevel", + } +] diff --git a/browser/components/securitylevel/content/securityLevel.js b/browser/components/securitylevel/content/securityLevel.js new file mode 100644 index 000000000000..4b27ddbde592 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevel.js @@ -0,0 +1,611 @@ +"use strict"; + +/* global AppConstants, Services, openPreferences, XPCOMUtils */ + +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", +}); + +const SecurityLevels = Object.freeze(["", "safest", "safer", "", "standard"]); + +XPCOMUtils.defineLazyGetter(this, "SecurityLevelStrings", () => { + let strings = { + // Generic terms + security_level: "Security Level", + security_level_standard: "Standard", + security_level_safer: "Safer", + security_level_safest: "Safest", + security_level_tooltip_standard: "Security Level: Standard", + security_level_tooltip_safer: "Security Level: Safer", + security_level_tooltip_safest: "Security Level: Safest", + // Shown only for custom level + security_level_custom: "Custom", + security_level_restore: "Restore Defaults", + security_level_learn_more: "Learn more", + // Panel + security_level_change: "Change…", + security_level_standard_summary: + "All browser and website features are enabled.", + security_level_safer_summary: + "Disables website features that are often dangerous, causing some sites to lose functionality.", + security_level_safest_summary: + "Only allows website features required for static sites and basic services. These changes affect images, media, and scripts.", + security_level_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.", + // Security level section in about:preferences#privacy + security_level_overview: + "Disable certain web features that can be used to attack your security and anonymity.", + security_level_list_safer: "At the safer setting:", + security_level_list_safest: "At the safest setting:", + // Strings for descriptions + security_level_js_https_only: "JavaScript is disabled on non-HTTPS sites.", + security_level_js_disabled: + "JavaScript is disabled by default on all sites.", + security_level_limit_typography: + "Some fonts and math symbols are disabled.", + security_level_limit_typography_svg: + "Some fonts, icons, math symbols, and images are disabled.", + security_level_limit_media: + "Audio and video (HTML5 media), and WebGL are click-to-play.", + }; + let bundle = null; + try { + bundle = Services.strings.createBundle( + "chrome://securitylevel/locale/securityLevel.properties" + ); + } catch (e) { + console.warn("Could not load the Security Level strings"); + } + if (bundle) { + for (const key of Object.keys(strings)) { + try { + strings[key] = bundle.GetStringFromName(key); + } catch (e) {} + } + } + return strings; +}); + +/* + 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 securitySliderLevel() { + const slider = this.securitySlider; + if (slider >= 1 && slider <= 4 && SecurityLevels[slider]) { + return SecurityLevels[slider]; + } + return null; + }, + + 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, + + _configUIFromPrefs() { + const securityLevelButton = this.button; + if (securityLevelButton != null) { + const level = SecurityLevelPrefs.securitySliderLevel; + if (!level) { + return; + } + const customStr = SecurityLevelPrefs.securityCustom ? "_custom" : ""; + securityLevelButton.setAttribute("level", `${level}${customStr}`); + securityLevelButton.setAttribute( + "tooltiptext", + SecurityLevelStrings[`security_level_tooltip_${level}`] + ); + } + }, + + /** + * The node for this button. + * + * Note, the returned element may be part of the DOM or may live in the + * toolbox palette, where it may be added later to the DOM through + * customization. + * + * @type {MozToolbarbutton} + */ + get button() { + // We first search in the DOM for the security level button. If it does not + // exist it may be in the toolbox palette. We still want to return the + // button in the latter case to allow it to be initialized or adjusted in + // case it is added back through customization. + return ( + document.getElementById("security-level-button") || + window.gNavToolbox.palette.querySelector("#security-level-button") + ); + }, + + get anchor() { + let button = this.button; + let anchor = button?.icon; + if (!anchor) { + return null; + } + + anchor.setAttribute("consumeanchor", button.id); + return anchor; + }, + + init() { + // Set a label to be be used as the accessible name, and to be shown in the + // overflow menu and during customization. + this.button?.setAttribute("label", SecurityLevelStrings.security_level); + // set the initial class based off of the current pref + this._configUIFromPrefs(); + + this._securityPrefsBranch = Services.prefs.getBranch( + "extensions.torbutton." + ); + this._securityPrefsBranch.addObserver("", this); + + SecurityLevelPanel.init(); + }, + + uninit() { + this._securityPrefsBranch.removeObserver("", this); + this._securityPrefsBranch = null; + + SecurityLevelPanel.uninit(); + }, + + observe(subject, topic, data) { + switch (topic) { + case "nsPref:changed": + if (data === "security_slider" || data === "security_custom") { + this._configUIFromPrefs(); + } + break; + } + }, + + // 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(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", + labelLevel: "label#securityLevel-level", + labelCustom: "label#securityLevel-custom", + summary: "description#securityLevel-summary", + restoreDefaults: "button#securityLevel-restoreDefaults", + advancedSecuritySettings: "button#securityLevel-advancedSecuritySettings", + // Selectors used only for l10n - remove them when switching to Fluent + header: "#securityLevel-header", + learnMore: "#securityLevel-panel .learnMore", + }), + + _populateXUL() { + let selectors = this._selectors; + + this._elements = { + panel: document.querySelector(selectors.panel), + icon: document.querySelector(selectors.icon), + labelLevel: document.querySelector(selectors.labelLevel), + labelCustom: document.querySelector(selectors.labelCustom), + summaryDescription: document.querySelector(selectors.summary), + restoreDefaultsButton: document.querySelector(selectors.restoreDefaults), + advancedSecuritySettings: document.querySelector( + selectors.advancedSecuritySettings + ), + header: document.querySelector(selectors.header), + learnMore: document.querySelector(selectors.learnMore), + }; + + this._elements.header.textContent = SecurityLevelStrings.security_level; + this._elements.labelCustom.setAttribute( + "value", + SecurityLevelStrings.security_level_custom + ); + this._elements.learnMore.setAttribute( + "value", + SecurityLevelStrings.security_level_learn_more + ); + this._elements.restoreDefaultsButton.textContent = + SecurityLevelStrings.security_level_restore; + this._elements.advancedSecuritySettings.textContent = + SecurityLevelStrings.security_level_change; + + this._elements.panel.addEventListener("onpopupshown", e => { + this.onPopupShown(e); + }); + this._elements.panel.addEventListener("onpopuphidden", e => { + this.onPopupHidden(e); + }); + this._elements.restoreDefaultsButton.addEventListener("command", () => { + this.restoreDefaults(); + }); + this._elements.advancedSecuritySettings.addEventListener("command", () => { + this.openAdvancedSecuritySettings(); + }); + this._populated = true; + this._configUIFromPrefs(); + }, + + _configUIFromPrefs() { + if (!this._populated) { + console.warn("_configUIFromPrefs before XUL was populated."); + return; + } + + // get security prefs + const level = SecurityLevelPrefs.securitySliderLevel; + const custom = SecurityLevelPrefs.securityCustom; + + // only visible when user is using custom settings + let labelCustomWarning = this._elements.labelCustom; + labelCustomWarning.hidden = !custom; + let buttonRestoreDefaults = this._elements.restoreDefaultsButton; + buttonRestoreDefaults.hidden = !custom; + + const summary = this._elements.summaryDescription; + // Descriptions change based on security level + if (level) { + this._elements.icon.setAttribute("level", level); + this._elements.labelLevel.setAttribute( + "value", + SecurityLevelStrings[`security_level_${level}`] + ); + summary.textContent = + SecurityLevelStrings[`security_level_${level}_summary`]; + } + // override the summary text with custom warning + if (custom) { + summary.textContent = SecurityLevelStrings.security_level_custom_summary; + } + }, + + init() { + this._securityPrefsBranch = Services.prefs.getBranch( + "extensions.torbutton." + ); + this._securityPrefsBranch.addObserver("", this); + }, + + uninit() { + this._securityPrefsBranch.removeObserver("", this); + this._securityPrefsBranch = null; + }, + + show() { + // we have to defer this until after the browser has finished init'ing + // before we can populate the panel + if (!this._populated) { + this._populateXUL(); + } + + this._elements.panel.hidden = false; + PanelMultiView.openPopup( + this._elements.panel, + SecurityLevelButton.anchor, + "bottomcenter topright", + 0, + 0, + false, + null + ).catch(Cu.reportError); + }, + + hide() { + PanelMultiView.hidePopup(this._elements.panel); + }, + + restoreDefaults() { + SecurityLevelPrefs.securityCustom = false; + // hide and reshow so that layout re-renders properly + this.hide(); + this.show(this._anchor); + }, + + openAdvancedSecuritySettings() { + openPreferences("privacy-securitylevel"); + this.hide(); + }, + + // callback when prefs change + observe(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(event) { + SecurityLevelButton.button.setAttribute("open", "true"); + }, + + // callback when the panel is hidden + onPopupHidden(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() { + const groupbox = document.querySelector("#securityLevel-groupbox"); + const radiogroup = groupbox.querySelector("#securityLevel-radiogroup"); + radiogroup.addEventListener( + "command", + SecurityLevelPreferences.selectSecurityLevel + ); + + groupbox.querySelector("h2").textContent = + SecurityLevelStrings.security_level; + groupbox.querySelector("#securityLevel-overview").textContent = + SecurityLevelStrings.security_level_overview; + groupbox + .querySelector("#securityLevel-learnMore") + .setAttribute("value", SecurityLevelStrings.security_level_learn_more); + + const populateRadioElements = (level, descr) => { + const vbox = groupbox.querySelector(`#securityLevel-vbox-${level}`); + vbox + .querySelector("radio") + .setAttribute("label", SecurityLevelStrings[`security_level_${level}`]); + vbox + .querySelector(".securityLevel-customWarning") + .setAttribute("value", SecurityLevelStrings.security_level_custom); + vbox.querySelector(".summary").textContent = + SecurityLevelStrings[`security_level_${level}_summary`]; + const labelRestoreDefaults = vbox.querySelector( + ".securityLevel-restoreDefaults" + ); + labelRestoreDefaults.setAttribute( + "value", + SecurityLevelStrings.security_level_restore + ); + labelRestoreDefaults.addEventListener( + "click", + SecurityLevelStrings.restoreDefaults + ); + if (descr) { + const descrList = vbox.querySelector(".securityLevel-descriptionList"); + // TODO: Add the elements in securityLevelPreferences.inc.xhtml again + // when we switch to Fluent + for (const text of descr) { + let elem = document.createXULElement("description"); + elem.textContent = text; + elem.className = "indent"; + descrList.append(elem); + } + } + }; + populateRadioElements("standard"); + populateRadioElements("safer", [ + SecurityLevelStrings.security_level_js_https_only, + SecurityLevelStrings.security_level_limit_typography, + SecurityLevelStrings.security_level_limit_media, + ]); + populateRadioElements("safest", [ + SecurityLevelStrings.security_level_js_disabled, + SecurityLevelStrings.security_level_limit_typography_svg, + SecurityLevelStrings.security_level_limit_media, + ]); + }, + + _configUIFromPrefs() { + // read our prefs + const securitySlider = SecurityLevelPrefs.securitySlider; + const securityCustom = SecurityLevelPrefs.securityCustom; + + // get our elements + const groupbox = document.querySelector("#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() { + // 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); + }, + + uninit() { + // unregister for pref change events + this._securityPrefsBranch.removeObserver("", this); + this._securityPrefsBranch = null; + }, + + // callback for when prefs change + observe(subject, topic, data) { + switch (topic) { + case "nsPref:changed": + if (data == "security_slider" || data == "security_custom") { + this._configUIFromPrefs(); + } + break; + } + }, + + selectSecurityLevel() { + // 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() { + 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 000000000000..38701250e9c9 --- /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 000000000000..96ee1ec0ca49 --- /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 000000000000..38cdbcb68afc --- /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 000000000000..c50acf0ae76c --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPanel.css @@ -0,0 +1,71 @@ +/* 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; +} + +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-inline-start: 1em !important; +} + +description#securityLevel-summary { + margin-top: 1em; + padding-inline-end: 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 000000000000..c485f819aba9 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml @@ -0,0 +1,44 @@ +<panel id="securityLevel-panel" + role="group" + type="arrow" + orient="vertical" + level="top" + hidden="true" + class="panel-no-padding"> + <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 + class="learnMore text-link" + href="about:manual#security-settings" + useoriginprincipal="true" + onclick="SecurityLevelPanel.hide();" + is="text-link"/> + <spacer/> + </hbox> + </vbox> + <hbox class="panel-footer"> + <button id="securityLevel-restoreDefaults"/> + <button id="securityLevel-advancedSecuritySettings" + default="true"/> + </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 000000000000..152c6489f365 --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPreferences.css @@ -0,0 +1,51 @@ +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; +} + +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 000000000000..07d9a1d3b32d --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml @@ -0,0 +1,62 @@ +<groupbox id="securityLevel-groupbox" data-category="panePrivacy" hidden="true"> + <label>html:h2/</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" + href="about:manual#security-settings" + useoriginprincipal="true"/> + </description> + <radiogroup id="securityLevel-radiogroup"> + <vbox id="securityLevel-vbox-standard"> + <hbox> + <radio value="standard"/> + <vbox> + <spacer flex="1"/> + <label class="securityLevel-customWarning"/> + <spacer flex="1"/> + </vbox> + <spacer flex="1"/> + </hbox> + <description flex="1" class="indent"> + <html:span class="summary tail-with-learn-more"/> + <label class="securityLevel-restoreDefaults learnMore text-link"/> + </description> + </vbox> + <vbox id="securityLevel-vbox-safer"> + <hbox> + <radio value="safer"/> + <vbox> + <spacer flex="1"/> + <label class="securityLevel-customWarning"/> + <spacer flex="1"/> + </vbox> + </hbox> + <description flex="1" class="indent"> + <html:span class="summary tail-with-learn-more"/> + <label class="securityLevel-restoreDefaults learnMore text-link"/> + </description> + <vbox class="securityLevel-descriptionList indent"> + </vbox> + </vbox> + <vbox id="securityLevel-vbox-safest"> + <hbox> + <radio value="safest"/> + <vbox> + <spacer flex="1"/> + <label class="securityLevel-customWarning"/> + <spacer flex="1"/> + </vbox> + </hbox> + <description flex="1" class="indent"> + <html:span class="summary tail-with-learn-more"/> + <label class="securityLevel-restoreDefaults learnMore text-link"/> + </description> + <vbox class="securityLevel-descriptionList 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 000000000000..ac8df00b1574 --- /dev/null +++ b/browser/components/securitylevel/jar.mn @@ -0,0 +1,11 @@ +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) + +securitylevel.jar: +# See New Identity for further information on how this works +% locale securitylevel en-US %locale/en-US/ + locale/en-US/securityLevel.properties (locale/en-US/securityLevel.properties) diff --git a/browser/components/securitylevel/locale/en-US/securityLevel.properties b/browser/components/securitylevel/locale/en-US/securityLevel.properties new file mode 100644 index 000000000000..ba047579d8a7 --- /dev/null +++ b/browser/components/securitylevel/locale/en-US/securityLevel.properties @@ -0,0 +1,30 @@ +# Generic terms +security_level = Security Level +security_level_standard = Standard +security_level_safer = Safer +security_level_safest = Safest +security_level_tooltip_standard = Security Level: Standard +security_level_tooltip_safer = Security Level: Safer +security_level_tooltip_safest = Security Level: Safest +# Shown only for custom level +security_level_custom = Custom +security_level_restore = Restore Defaults +security_level_learn_more = Learn more + +# Panel +security_level_change = Change… +security_level_standard_summary = All browser and website features are enabled. +security_level_safer_summary = Disables website features that are often dangerous, causing some sites to lose functionality. +security_level_safest_summary = Only allows website features required for static sites and basic services. These changes affect images, media, and scripts. +security_level_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. + +## Security level section in about:preferences#privacy +security_level_overview = Disable certain web features that can be used to attack your security and anonymity. +security_level_list_safer = At the safer setting: +security_level_list_safest = At the safest setting: +# Strings for descriptions +security_level_js_https_only = JavaScript is disabled on non-HTTPS sites. +security_level_js_disabled = JavaScript is disabled by default on all sites. +security_level_limit_typography = Some fonts and math symbols are disabled. +security_level_limit_typography_svg = Some fonts, icons, math symbols, and images are disabled. +security_level_limit_media = Audio and video (HTML5 media), and WebGL are click-to-play. diff --git a/browser/components/securitylevel/moz.build b/browser/components/securitylevel/moz.build new file mode 100644 index 000000000000..33573a3706f9 --- /dev/null +++ b/browser/components/securitylevel/moz.build @@ -0,0 +1,13 @@ +JAR_MANIFESTS += ["jar.mn"] + +EXTRA_JS_MODULES += [ + "SecurityLevel.jsm", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXTRA_COMPONENTS += [ + "SecurityLevel.manifest", +] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index bdc712aaa8e7..61dc9789a2eb 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -244,6 +244,11 @@ @RESPATH@/browser/chrome/icons/default/default128.png #endif
+; Base Browser +@RESPATH@/browser/chrome/securitylevel.manifest +@RESPATH@/browser/chrome/securitylevel/ +@RESPATH@/browser/components/SecurityLevel.manifest + ; [DevTools Startup Files] @RESPATH@/browser/chrome/devtools-startup@JAREXT@ @RESPATH@/browser/chrome/devtools-startup.manifest diff --git a/browser/themes/shared/customizableui/panelUI-shared.css b/browser/themes/shared/customizableui/panelUI-shared.css index fcb62bcff1d9..76839ce2df07 100644 --- a/browser/themes/shared/customizableui/panelUI-shared.css +++ b/browser/themes/shared/customizableui/panelUI-shared.css @@ -1323,7 +1323,8 @@ panelview .toolbarbutton-1 { #editBookmarkPanel toolbarseparator, #downloadsFooterButtons > 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); diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 4ec5b10c4da8..af1b7796e6d2 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -812,6 +812,7 @@ package org.mozilla.geckoview { method @Nullable public Rect getScreenSizeOverride(); method public boolean getSpoofEnglish(); method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate(); + method public int getTorSecurityLevel(); method public boolean getUseMaxScreenDepth(); method public boolean getWebFontsEnabled(); method public boolean getWebManifestEnabled(); @@ -833,6 +834,7 @@ package org.mozilla.geckoview { 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); field public static final int ALLOW_ALL = 0; @@ -874,6 +876,7 @@ package org.mozilla.geckoview { 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); 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 b96fbd15cf5d..48d13963d0b6 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 @@ -464,6 +464,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { getSettings().mSpoofEnglish.set(flag ? 2 : 1); 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;
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 3d5527beccc5982dc1cd761de21bf90a251aeead Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon Jul 25 10:40:35 2022 +0200
Bug 40926: Implemented the New Identity feature --- browser/app/profile/001-base-profile.js | 2 +- browser/base/content/appmenu-viewcache.inc.xhtml | 4 + browser/base/content/browser-menubar.inc | 4 + browser/base/content/browser-sets.inc | 1 + browser/base/content/browser.js | 10 + browser/base/content/navigator-toolbox.inc.xhtml | 2 + browser/components/moz.build | 1 + .../components/newidentity/content/newidentity.js | 558 +++++++++++++++++++++ browser/components/newidentity/jar.mn | 13 + .../locale/en-US/newIdentity.properties | 8 + browser/components/newidentity/moz.build | 1 + browser/installer/package-manifest.in | 2 + browser/themes/shared/icons/new_identity.svg | 9 + browser/themes/shared/jar.inc.mn | 2 + browser/themes/shared/toolbarbutton-icons.css | 4 + 15 files changed, 620 insertions(+), 1 deletion(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index 1858d29c98c1..6e443412ab34 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -324,7 +324,7 @@ pref("extensions.webextensions.restrictedDomains", ""); 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","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-window-button","sav [...] +pref("browser.uiCustomization.state", "{"placements":{"widget-overflow-fixed-list":[],"PersonalToolbar":["personal-bookmarks"],"nav-bar":["back-button","forward-button","stop-reload-button","urlbar-container","security-level-button","new-identity-button","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls","zoom-controls","n [...]
// Enforce certificate pinning, see: https://bugs.torproject.org/16206 pref("security.cert_pinning.enforcement_level", 2); diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml index 29269b96ee06..a67f89bd5f70 100644 --- a/browser/base/content/appmenu-viewcache.inc.xhtml +++ b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -177,6 +177,10 @@ class="subviewbutton" command="Browser:RestoreLastSession"/> <toolbarseparator/> + <toolbarbutton id="appMenu-new-identity" + class="subviewbutton" + key="new-identity-key"/> + <toolbarseparator/> <toolbarbutton id="appMenuClearRecentHistory" data-l10n-id="appmenu-clear-history" class="subviewbutton" diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index 1adea92456c7..973f3c5c98f6 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -28,6 +28,10 @@ <menuitem id="menu_newPrivateWindow" command="Tools:PrivateBrowsing" key="key_privatebrowsing" data-l10n-id="menu-file-new-private-window"/> + <menuseparator/> + <menuitem id="menu_newIdentity" + key="new-identity-key"/> + <menuseparator/> <menuitem id="menu_openLocation" hidden="true" command="Browser:OpenLocation" diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index 91fac883c66a..17edb35baf6a 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -388,4 +388,5 @@ modifiers="accel,alt" internal="true"/> #endif + <key id="new-identity-key" modifiers="accel shift" key="U" oncommand="NewIdentityButton.onCommand(event)"/> </keyset> diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index ac8b1d100ad8..d14f5c1439dc 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -228,6 +228,11 @@ XPCOMUtils.defineLazyScriptGetter( ["SecurityLevelButton"], "chrome://browser/content/securitylevel/securityLevel.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["NewIdentityButton"], + "chrome://browser/content/newidentity.js" +); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", @@ -1780,6 +1785,9 @@ var gBrowserInit = { // Init the SecuritySettingsButton SecurityLevelButton.init();
+ // Init the NewIdentityButton + NewIdentityButton.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 @@ -2502,6 +2510,8 @@ var gBrowserInit = {
SecurityLevelButton.uninit();
+ NewIdentityButton.uninit(); + gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) { diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 4e216ac82508..cadf68c91679 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -537,6 +537,8 @@ ondragover="newWindowButtonObserver.onDragOver(event)" ondragenter="newWindowButtonObserver.onDragOver(event)"/>
+ <toolbarbutton id="new-identity-button" class="toolbarbutton-1 chromeclass-toolbar-additional"/> + <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional" observes="View:FullScreen" type="checkbox" diff --git a/browser/components/moz.build b/browser/components/moz.build index 09c7d2a3767e..12a2187c638b 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -38,6 +38,7 @@ DIRS += [ "extensions", "pagedata", "migration", + "newidentity", "newtab", "originattributes", "places", diff --git a/browser/components/newidentity/content/newidentity.js b/browser/components/newidentity/content/newidentity.js new file mode 100644 index 000000000000..67a00dbc6f75 --- /dev/null +++ b/browser/components/newidentity/content/newidentity.js @@ -0,0 +1,558 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["NewIdentityButton"]; + +/* globals CustomizableUI Services gFindBarInitialized gFindBar + OpenBrowserWindow PrivateBrowsingUtils XPCOMUtils + */ + +XPCOMUtils.defineLazyGetter(this, "NewIdentityStrings", () => { + const brandBundle = Services.strings.createBundle( + "chrome://branding/locale/brand.properties" + ); + const brandShortName = brandBundle.GetStringFromName("brandShortName"); + + let strings = { + new_identity: "New Identity", + new_identity_sentence_case: "New identity", + new_identity_prompt: `${brandShortName} will close all windows and tabs. All website sessions will be lost. \nRestart ${brandShortName} now to reset your identity?`, + new_identity_ask_again: "Never ask me again", + new_identity_menu_accesskey: "I", + }; + let bundle = null; + try { + bundle = Services.strings.createBundle( + "chrome://newidentity/locale/newIdentity.properties" + ); + } catch (e) { + console.warn("Could not load the New Identity strings"); + } + if (bundle) { + for (const key of Object.keys(strings)) { + try { + strings[key] = bundle.GetStringFromName(key); + } catch (e) {} + } + strings.new_identity_prompt = strings.new_identity_prompt.replaceAll( + "%S", + brandShortName + ); + } + return strings; +}); + +// Use a lazy getter because NewIdentityButton is declared more than once +// otherwise. +XPCOMUtils.defineLazyGetter(this, "NewIdentityButton", () => { + // Logger adapted from CustomizableUI.jsm + const logger = (() => { + const { ConsoleAPI } = ChromeUtils.import( + "resource://gre/modules/Console.jsm" + ); + const consoleOptions = { + maxLogLevel: "info", + prefix: "NewIdentity", + }; + return new ConsoleAPI(consoleOptions); + })(); + + const topics = Object.freeze({ + newIdentityRequested: "new-identity-requested", + }); + + class NewIdentityImpl { + async run() { + logger.debug("Disabling JS"); + this.disableAllJS(); + await this.clearState(); + this.broadcast(); + this.openNewWindow(); + this.closeOldWindow(); + } + + // Disable JS (as a defense-in-depth measure) + + disableAllJS() { + logger.info("Disabling JavaScript"); + const enumerator = Services.wm.getEnumerator("navigator:browser"); + while (enumerator.hasMoreElements()) { + const win = enumerator.getNext(); + this.disableWindowJS(win); + } + } + + disableWindowJS(win) { + const browsers = win.gBrowser?.browsers || []; + for (const browser of browsers) { + if (!browser) { + continue; + } + this.disableBrowserJS(browser); + try { + browser.webNavigation?.stop(browser.webNavigation.STOP_ALL); + } catch (e) { + logger.warn("Could not stop navigation", e, browser.currentURI); + } + } + } + + disableBrowserJS(browser) { + if (!browser) { + return; + } + // Does the following still apply? + // Solution from: https://bugzilla.mozilla.org/show_bug.cgi?id=409737 + // XXX: This kills the entire window. We need to redirect + // focus and inform the user via a lightbox. + const eventSuppressor = browser.contentWindow?.windowUtils; + if (browser.browsingContext) { + browser.browsingContext.allowJavascript = false; + } + try { + // My estimation is that this does not get the inner iframe windows, + // but that does not matter, because iframes should be destroyed + // on the next load. + // Should we log when browser.contentWindow is null? + if (browser.contentWindow) { + browser.contentWindow.name = null; + browser.contentWindow.window.name = null; + } + } catch (e) { + logger.warn("Failed to reset window.name", e); + } + eventSuppressor?.suppressEventHandling(true); + } + + // Clear state + + async clearState() { + logger.info("Clearing the state"); + this.closeTabs(); + this.clearSearchBar(); + this.clearPrivateSessionHistory(); + this.clearHTTPAuths(); + this.clearCryptoTokens(); + this.clearOCSPCache(); + this.clearSecuritySettings(); + this.clearImageCaches(); + this.clearStorage(); + this.clearPreferencesAndPermissions(); + await this.clearData(); + this.clearConnections(); + this.clearPrivateSession(); + } + + clearSiteSpecificZoom() { + Services.prefs.setBoolPref( + "browser.zoom.siteSpecific", + !Services.prefs.getBoolPref("browser.zoom.siteSpecific") + ); + Services.prefs.setBoolPref( + "browser.zoom.siteSpecific", + !Services.prefs.getBoolPref("browser.zoom.siteSpecific") + ); + } + + closeTabs() { + logger.info("Closing tabs"); + if ( + !Services.prefs.getBoolPref("extensions.torbutton.close_newnym", true) + ) { + logger.info("Not closing tabs"); + return; + } + // TODO: muck around with browser.tabs.warnOnClose.. maybe.. + logger.info("Closing tabs..."); + const enumerator = Services.wm.getEnumerator("navigator:browser"); + const windowsToClose = []; + while (enumerator.hasMoreElements()) { + const win = enumerator.getNext(); + const browser = win.gBrowser; + if (!browser) { + logger.warn("No browser for possible window to close"); + continue; + } + const tabsToRemove = []; + for (const b of browser.browsers) { + const tab = browser.getTabForBrowser(b); + if (tab) { + tabsToRemove.push(tab); + } else { + logger.warn("Browser has a null tab", b); + } + } + if (win == window) { + browser.addWebTab("about:blank"); + } else { + // It is a bad idea to alter the window list while iterating + // over it, so add this window to an array and close it later. + windowsToClose.push(win); + } + // Close each tab except the new blank one that we created. + tabsToRemove.forEach(aTab => browser.removeTab(aTab)); + } + // Close all XUL windows except this one. + logger.info("Closing windows..."); + windowsToClose.forEach(aWin => aWin.close()); + logger.info("Closed all tabs"); + + // This clears the undo tab history. + const tabs = Services.prefs.getIntPref( + "browser.sessionstore.max_tabs_undo" + ); + Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo", 0); + Services.prefs.setIntPref("browser.sessionstore.max_tabs_undo", tabs); + } + + clearSearchBar() { + logger.info("Clearing searchbox"); + // Bug #10800: Trying to clear search/find can cause exceptions + // in unknown cases. Just log for now. + try { + const searchBar = window.document.getElementById("searchbar"); + if (searchBar) { + searchBar.textbox.reset(); + } + } catch (e) { + logger.error("Exception on clearing search box", e); + } + try { + if (gFindBarInitialized) { + const findbox = gFindBar.getElement("findbar-textbox"); + findbox.reset(); + gFindBar.close(); + } + } catch (e) { + logger.error("Exception on clearing find bar", e); + } + } + + clearPrivateSessionHistory() { + logger.info("Emitting Private Browsing Session clear event"); + Services.obs.notifyObservers(null, "browser:purge-session-history"); + } + + clearHTTPAuths() { + if ( + !Services.prefs.getBoolPref( + "extensions.torbutton.clear_http_auth", + true + ) + ) { + logger.info("Skipping HTTP Auths, because disabled"); + return; + } + logger.info("Clearing HTTP Auths"); + const auth = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager + ); + auth.clearAll(); + } + + clearCryptoTokens() { + logger.info("Clearing Crypto Tokens"); + // Clear all crypto auth tokens. This includes calls to PK11_LogoutAll(), + // nsNSSComponent::LogoutAuthenticatedPK11() and clearing the SSL session + // cache. + const sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + sdr.logoutAndTeardown(); + } + + clearOCSPCache() { + // nsNSSComponent::Observe() watches security.OCSP.enabled, which calls + // setValidationOptions(), which in turn calls setNonPkixOcspEnabled() which, + // if security.OCSP.enabled is set to 0, calls CERT_DisableOCSPChecking(), + // which calls CERT_ClearOCSPCache(). + // See: https://mxr.mozilla.org/comm-esr24/source/mozilla/security/manager/ssl/src/n... + const ocsp = Services.prefs.getIntPref("security.OCSP.enabled"); + Services.prefs.setIntPref("security.OCSP.enabled", 0); + Services.prefs.setIntPref("security.OCSP.enabled", ocsp); + } + + clearSecuritySettings() { + // Clear site security settings + const sss = Cc["@mozilla.org/ssservice;1"].getService( + Ci.nsISiteSecurityService + ); + sss.clearAll(); + } + + clearImageCaches() { + logger.info("Clearing Image Cache"); + // In Firefox 18 and newer, there are two image caches: one that is used + // for regular browsing, and one that is used for private browsing. + this.clearImageCacheRB(); + this.clearImageCachePB(); + } + + clearImageCacheRB() { + try { + const imgTools = Cc["@mozilla.org/image/tools;1"].getService( + Ci.imgITools + ); + const imgCache = imgTools.getImgCacheForDocument(null); + // Evict all but chrome cache + imgCache.clearCache(false); + } catch (e) { + // FIXME: This can happen in some rare cases involving XULish image data + // in combination with our image cache isolation patch. Sure isn't + // a good thing, but it's not really a super-cookie vector either. + // We should fix it eventually. + logger.error("Exception on image cache clearing", e); + } + } + + clearImageCachePB() { + const imgTools = Cc["@mozilla.org/image/tools;1"].getService( + Ci.imgITools + ); + try { + // Try to clear the private browsing cache. To do so, we must locate a + // content document that is contained within a private browsing window. + let didClearPBCache = false; + const enumerator = Services.wm.getEnumerator("navigator:browser"); + while (!didClearPBCache && enumerator.hasMoreElements()) { + const win = enumerator.getNext(); + let browserDoc = win.document.documentElement; + if (!browserDoc.hasAttribute("privatebrowsingmode")) { + continue; + } + const tabbrowser = win.gBrowser; + if (!tabbrowser) { + continue; + } + for (const browser of tabbrowser.browsers) { + const doc = browser.contentDocument; + if (doc) { + const imgCache = imgTools.getImgCacheForDocument(doc); + // Evict all but chrome cache + imgCache.clearCache(false); + didClearPBCache = true; + break; + } + } + } + } catch (e) { + logger.error("Exception on private browsing image cache clearing", e); + } + } + + clearStorage() { + logger.info("Clearing Disk and Memory Caches"); + try { + Services.cache2.clear(); + } catch (e) { + logger.error("Exception on cache clearing", e); + } + + logger.info("Clearing Cookies and DOM Storage"); + Services.cookies.removeAll(); + } + + clearPreferencesAndPermissions() { + logger.info("Clearing Content Preferences"); + ChromeUtils.defineModuleGetter( + this, + "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm" + ); + const pbCtxt = PrivateBrowsingUtils.privacyContextFromWindow(window); + const cps = Cc["@mozilla.org/content-pref/service;1"].getService( + Ci.nsIContentPrefService2 + ); + cps.removeAllDomains(pbCtxt); + this.clearSiteSpecificZoom(); + + logger.info("Clearing permissions"); + try { + Services.perms.removeAll(); + } catch (e) { + // Actually, this catch does not appear to be needed. Leaving it in for + // safety though. + logger.error("Cannot clear permissions", e); + } + + logger.info("Syncing prefs"); + // Force prefs to be synced to disk + Services.prefs.savePrefFile(null); + } + + async clearData() { + logger.info("Calling the clearDataService"); + const flags = + Services.clearData.CLEAR_ALL ^ Services.clearData.CLEAR_PASSWORDS; + return new Promise((resolve, reject) => { + Services.clearData.deleteData(flags, { + onDataDeleted(code) { + if (code !== Cr.NS_OK) { + logger.error(`Error while calling the clearDataService: ${code}`); + } + // We always resolve, because we do not want to interrupt the new + // identity procedure. + resolve(); + }, + }); + }); + } + + clearConnections() { + logger.info("Closing open connections"); + // Clear keep-alive + Services.obs.notifyObservers(this, "net:prune-all-connections"); + } + + clearPrivateSession() { + logger.info("Ending any remaining private browsing sessions."); + Services.obs.notifyObservers(null, "last-pb-context-exited"); + } + + // Broadcast as a hook to clear other data + + broadcast() { + logger.info("Broadcasting the new identity"); + Services.obs.notifyObservers({}, topics.newIdentityRequested); + } + + // Window management + + openNewWindow() { + logger.info("Opening a new window"); + // Open a new window with the default homepage + // We could pass {private: true} but we do not because we enforce + // browser.privatebrowsing.autostart = true. + // What about users that change settings? + OpenBrowserWindow(); + } + + closeOldWindow() { + logger.info("Closing the old window"); + + // Run garbage collection and cycle collection after window is gone. + // This ensures that blob URIs are forgotten. + window.addEventListener("unload", function(event) { + logger.debug("Initiating New Identity GC pass"); + // Clear out potential pending sInterSliceGCTimer: + window.windowUtils.runNextCollectorTimer(); + // Clear out potential pending sICCTimer: + window.windowUtils.runNextCollectorTimer(); + // Schedule a garbage collection in 4000-1000ms... + window.windowUtils.garbageCollect(); + // To ensure the GC runs immediately instead of 4-10s from now, we need + // to poke it at least 11 times. + // We need 5 pokes for GC, 1 poke for the interSliceGC, and 5 pokes for + // CC. + // See nsJSContext::RunNextCollectorTimer() in + // https://mxr.mozilla.org/mozilla-central/source/dom/base/nsJSEnvironment.cpp#.... + // XXX: We might want to make our own method for immediate full GC... + for (let poke = 0; poke < 11; poke++) { + window.windowUtils.runNextCollectorTimer(); + } + // And now, since the GC probably actually ran *after* the CC last time, + // run the whole thing again. + window.windowUtils.garbageCollect(); + for (let poke = 0; poke < 11; poke++) { + window.windowUtils.runNextCollectorTimer(); + } + logger.debug("Completed New Identity GC pass"); + }); + + // Close the current window for added safety + window.close(); + } + } + + let newIdentityInProgress = false; + return { + topics, + + init() { + // We first search in the DOM for the identity button. If it does not + // exist it may be in the toolbox palette. In the latter case we still + // need to initialize the button in case it is added back later through + // customization. + const button = + document.getElementById("new-identity-button") || + window.gNavToolbox.palette.querySelector("#new-identity-button"); + if (button) { + button.setAttribute("tooltiptext", NewIdentityStrings.new_identity); + // Include an equal label, shown in the overflow menu or during + // customization. + button.setAttribute("label", NewIdentityStrings.new_identity); + button.addEventListener("command", () => { + this.onCommand(); + }); + } + const viewCache = document.getElementById("appMenu-viewCache").content; + const appButton = viewCache.querySelector("#appMenu-new-identity"); + if (appButton) { + appButton.setAttribute( + "label", + NewIdentityStrings.new_identity_sentence_case + ); + appButton.addEventListener("command", () => { + this.onCommand(); + }); + } + const menu = document.querySelector("#menu_newIdentity"); + if (menu) { + menu.setAttribute("label", NewIdentityStrings.new_identity); + menu.setAttribute( + "accesskey", + NewIdentityStrings.new_identity_menu_accesskey + ); + menu.addEventListener("command", () => { + this.onCommand(); + }); + } + }, + + uninit() {}, + + async onCommand() { + try { + // Ignore if there's a New Identity in progress to avoid race + // conditions leading to failures (see bug 11783 for an example). + if (newIdentityInProgress) { + return; + } + newIdentityInProgress = true; + + const prefConfirm = "extensions.torbutton.confirm_newnym"; + const shouldConfirm = Services.prefs.getBoolPref(prefConfirm, true); + if (shouldConfirm) { + // Display two buttons, both with string titles. + const flags = Services.prompt.STD_YES_NO_BUTTONS; + const askAgain = { value: false }; + const confirmed = + Services.prompt.confirmEx( + null, + "", + NewIdentityStrings.new_identity_prompt, + flags, + null, + null, + null, + NewIdentityStrings.new_identity_ask_again, + askAgain + ) == 0; + Services.prefs.setBoolPref(prefConfirm, !askAgain.value); + if (!confirmed) { + return; + } + } + + const impl = new NewIdentityImpl(); + await impl.run(); + } catch (e) { + // If something went wrong make sure we have the New Identity button + // enabled (again). + logger.error("Unexpected error", e); + window.alert("New Identity unexpected error: " + e); + } finally { + newIdentityInProgress = false; + } + }, + }; +}); diff --git a/browser/components/newidentity/jar.mn b/browser/components/newidentity/jar.mn new file mode 100644 index 000000000000..57b30b32c0e1 --- /dev/null +++ b/browser/components/newidentity/jar.mn @@ -0,0 +1,13 @@ +browser.jar: + content/browser/newidentity.js (content/newidentity.js) + +newidentity.jar: +# We need to list at least one locale here, to make Firefox load the localized +# copy of properties at chrome://newidentity/locale/newIdentity.properties. +# Ideally, we should use @AB_CD@.jar to automatically copy all the locales +# Firefox is built with. But we only provide English here, and injecting the +# translated files directly to the omni.ja works better for us, for the time +# being. In addition to inject the properties files, we also add the +# corresponding locale line to chrome/chrome.manifest. +% locale newidentity en-US %locale/en-US/ + locale/en-US/newIdentity.properties (locale/en-US/newIdentity.properties) diff --git a/browser/components/newidentity/locale/en-US/newIdentity.properties b/browser/components/newidentity/locale/en-US/newIdentity.properties new file mode 100644 index 000000000000..54eeca135a5e --- /dev/null +++ b/browser/components/newidentity/locale/en-US/newIdentity.properties @@ -0,0 +1,8 @@ +new_identity = New Identity +# This is the string for the hamburger menu +new_identity_sentence_case = New identity +# %S is the application name. Keep it as a placeholder +new_identity_prompt = %S will close all windows and tabs. All website sessions will be lost. \nRestart %S now to reset your identity? +new_identity_ask_again = Never ask me again +# Shown in the File menu (use Alt to show File, if you do not see) +new_identity_menu_accesskey = I diff --git a/browser/components/newidentity/moz.build b/browser/components/newidentity/moz.build new file mode 100644 index 000000000000..2661ad7cb9f3 --- /dev/null +++ b/browser/components/newidentity/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 61dc9789a2eb..9b5a82add399 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -245,6 +245,8 @@ #endif
; Base Browser +@RESPATH@/browser/chrome/newidentity.manifest +@RESPATH@/browser/chrome/newidentity/ @RESPATH@/browser/chrome/securitylevel.manifest @RESPATH@/browser/chrome/securitylevel/ @RESPATH@/browser/components/SecurityLevel.manifest diff --git a/browser/themes/shared/icons/new_identity.svg b/browser/themes/shared/icons/new_identity.svg new file mode 100644 index 000000000000..096ff169c02f --- /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 4a88a1cc318d..ae8d82c09a88 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -259,3 +259,5 @@ skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg)
skin/classic/browser/syncedtabs/sidebar.css (../shared/syncedtabs/sidebar.css) + + skin/classic/browser/new_identity.svg (../shared/icons/new_identity.svg) diff --git a/browser/themes/shared/toolbarbutton-icons.css b/browser/themes/shared/toolbarbutton-icons.css index a4d40af3b421..8e285fdfd7c2 100644 --- a/browser/themes/shared/toolbarbutton-icons.css +++ b/browser/themes/shared/toolbarbutton-icons.css @@ -263,6 +263,10 @@ toolbar { list-style-image: url("chrome://browser/skin/new-tab.svg"); }
+#new-identity-button { + list-style-image: url("chrome://browser/skin/new_identity.svg"); +} + #privatebrowsing-button { list-style-image: url("chrome://browser/skin/privateBrowsing.svg"); }
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 4df1b05a15fbc1c7cf6b04391970f2682af49b16 Author: Richard Pospesel richard@torproject.org AuthorDate: Mon Aug 1 17:56:45 2022 +0000
Bug 41089: Add tor-browser build scripts + Makefile to tor-browser --- .gitignore | 3 ++ tools/torbrowser/Makefile | 47 +++++++++++++++++++++++++++ tools/torbrowser/bridges.js | 77 +++++++++++++++++++++++++++++++++++++++++++++ tools/torbrowser/build.sh | 7 +++++ tools/torbrowser/clobber.sh | 6 ++++ tools/torbrowser/config.sh | 6 ++++ tools/torbrowser/deploy.sh | 23 ++++++++++++++ tools/torbrowser/fataar.sh | 34 ++++++++++++++++++++ tools/torbrowser/fetch.sh | 30 ++++++++++++++++++ tools/torbrowser/ide.sh | 7 +++++ tools/torbrowser/jslint.sh | 7 +++++ 11 files changed, 247 insertions(+)
diff --git a/.gitignore b/.gitignore index b409ec583766..b2b8b720c178 100644 --- a/.gitignore +++ b/.gitignore @@ -193,3 +193,6 @@ config/external/icu4x # Ignore Storybook generated files browser/components/storybook/node_modules/ browser/components/storybook/storybook-static/ + +# Ignore binary base of tor browser +.binaries diff --git a/tools/torbrowser/Makefile b/tools/torbrowser/Makefile new file mode 100644 index 000000000000..be6f44c52ce6 --- /dev/null +++ b/tools/torbrowser/Makefile @@ -0,0 +1,47 @@ +.DEFAULT_GOAL := all + +# https://stackoverflow.com/questions/18136918/how-to-get-current-relative-dir... +mkfile_path := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +DEV_ROOT = $(mkfile_path)/../.. +BINARIES = $(DEV_ROOT)/.binaries +BUILD_OUTPUT = $(DEV_ROOT)/obj-x86_64-pc-linux-gnu + +config: + ./config.sh $(DEV_ROOT) + +ide-vscode: + ./ide.sh vscode $(DEV_ROOT) + +ide-eclipse: + ./ide.sh eclipse $(DEV_ROOT) + +ide-visualstudio: + ./ide.sh visualstudio $(DEV_ROOT) + +fetch: + ./fetch.sh $(BINARIES) + +build: + ./build.sh $(DEV_ROOT) + +deploy: + ./deploy.sh $(BINARIES) $(BUILD_OUTPUT) + +fat-aar: + ./fataar.sh $(DEV_ROOT) $(ARCHS) + +all: build deploy + +run: + $(BINARIES)/dev/Browser/start-tor-browser -v + +jslint: + ./jslint.sh $(DEV_ROOT) $(JS) + +clobber: + ./clobber.sh $(DEV_ROOT) + +clean: + rm -rf $(BUILD_OUTPUT) + diff --git a/tools/torbrowser/bridges.js b/tools/torbrowser/bridges.js new file mode 100644 index 000000000000..e8f11a36c401 --- /dev/null +++ b/tools/torbrowser/bridges.js @@ -0,0 +1,77 @@ +pref("extensions.torlauncher.default_bridge_recommended_type", "obfs4"); + +// Default bridges. +pref( + "extensions.torlauncher.default_bridge.obfs4.1", + "obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.2", + "obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.3", + "obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.4", + "obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.5", + "obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.6", + "obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.7", + "obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.8", + "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.9", + "obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.10", + "obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.11", + "obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.12", + "obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.13", + "obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.14", + "obfs4 [2a0c:4d80:42:702::1]:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.15", + "obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0" +); +pref( + "extensions.torlauncher.default_bridge.obfs4.16", + "obfs4 185.100.87.30:443 5B403DFE34F4872EB027059CECAE30B0C864B3A2 cert=bWUdFUe8io9U6JkSLoGAvSAUDcB779/shovCYmYAQb/pW/iEAMZtO/lCd94OokOF909TPA iat-mode=2" +); + +pref( + "extensions.torlauncher.default_bridge.meek-azure.1", + "meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" +); + +pref( + "extensions.torlauncher.default_bridge.snowflake.1", + "snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72" +); diff --git a/tools/torbrowser/build.sh b/tools/torbrowser/build.sh new file mode 100755 index 000000000000..e53dbc5000bc --- /dev/null +++ b/tools/torbrowser/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +DEV_ROOT=$1 + +cd $DEV_ROOT +./mach build +./mach build stage-package diff --git a/tools/torbrowser/clobber.sh b/tools/torbrowser/clobber.sh new file mode 100755 index 000000000000..5073454b23c1 --- /dev/null +++ b/tools/torbrowser/clobber.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +DEV_ROOT=$1 + +cd $DEV_ROOT +./mach clobber diff --git a/tools/torbrowser/config.sh b/tools/torbrowser/config.sh new file mode 100755 index 000000000000..d35311961379 --- /dev/null +++ b/tools/torbrowser/config.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +DEV_ROOT=$1 + +cd $DEV_ROOT +./mach configure diff --git a/tools/torbrowser/deploy.sh b/tools/torbrowser/deploy.sh new file mode 100755 index 000000000000..9f2ebd58cbe3 --- /dev/null +++ b/tools/torbrowser/deploy.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e +BINARIES=$1 +BUILD_OUTPUT=$2 + +SCRIPT_DIR=$(realpath "$(dirname "$0")") + +# Add built-in bridges +mkdir -p $BUILD_OUTPUT/_omni/defaults/preferences +cat $BUILD_OUTPUT/dist/bin/browser/defaults/preferences/000-tor-browser.js $SCRIPT_DIR/bridges.js >> $BUILD_OUTPUT/_omni/defaults/preferences/000-tor-browser.js +cd $BUILD_OUTPUT/_omni && zip -Xmr $BUILD_OUTPUT/dist/firefox/browser/omni.ja defaults/preferences/000-tor-browser.js +rm -rf $BUILD_OUTPUT/_omni + +# Repackage the manual +# rm -rf $BUILD_OUTPUT/_omni +# mkdir $BUILD_OUTPUT/_omni +# unzip $BINARIES/dev/Browser/browser/omni.ja -d $BUILD_OUTPUT/_omni +# cd $BUILD_OUTPUT/_omni && zip -Xmr $BUILD_OUTPUT/dist/firefox/browser/omni.ja chrome/browser/content/browser/manual +# rm -rf $BUILD_OUTPUT/_omni + +# copy binaries +cp -r $BUILD_OUTPUT/dist/firefox/* $BINARIES/dev/Browser +rm -rf $BINARIES/dev/Browser/TorBrowser/Data/Browser/profile.default/startupCache diff --git a/tools/torbrowser/fataar.sh b/tools/torbrowser/fataar.sh new file mode 100755 index 000000000000..0f15a16e9cd9 --- /dev/null +++ b/tools/torbrowser/fataar.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -e +DEV_ROOT=$1 +ARCHS=$2 + +cd $DEV_ROOT + +glue="" +if [[ "$ARCHS" == *"armv7"* ]]; then + export MOZ_ANDROID_FAT_AAR_ARMEABI_V7A=$DEV_ROOT/obj-arm-linux-androideabi/gradle/build/mobile/android/geckoview/outputs/aar/geckoview-withGeckoBinaries-debug.aar + glue="$glue,armeabi-v7a" +fi +if [[ "$ARCHS" == *"aarch64"* ]]; then + export MOZ_ANDROID_FAT_AAR_ARM64_V8A=$DEV_ROOT/obj-aarch64-linux-android/gradle/build/mobile/android/geckoview/outputs/aar/geckoview-withGeckoBinaries-debug.aar + glue="$glue,arm64-v8a" +fi +if [[ "$ARCHS" == *"x86"* ]]; then + export MOZ_ANDROID_FAT_AAR_X86=$DEV_ROOT/obj-i386-linux-android/gradle/build/mobile/android/geckoview/outputs/aar/geckoview-withGeckoBinaries-debug.aar + glue="$glue,x86" +fi +if [[ "$ARCHS" == *"x86_64"* ]]; then + export MOZ_ANDROID_FAT_AAR_X86_64=$DEV_ROOT/obj-x86_64-linux-android/gradle/build/mobile/android/geckoview/outputs/aar/geckoview-withGeckoBinaries-debug.aar + glue="$glue,x86_64" +fi +if [ -z "$glue" ]; then + echo "The architectures have not specified or are not valid." + echo "Usage: make fat-aar ARCHS="$archs"" + echo "Valid architectures are armv7 aarch64 x86 x86_64, and must be separated with a space." + exit 1 +fi +export MOZ_ANDROID_FAT_AAR_ARCHITECTURES=${glue:1} + +MOZCONFIG=mozconfig-android-all-dev ./mach configure +MOZCONFIG=mozconfig-android-all-dev ./mach build diff --git a/tools/torbrowser/fetch.sh b/tools/torbrowser/fetch.sh new file mode 100755 index 000000000000..5b5c627c0c34 --- /dev/null +++ b/tools/torbrowser/fetch.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -e + +BINARIES_DIR=$1 + +# download the current downloads.json +wget https://aus1.torproject.org/torbrowser/update_3/alpha/downloads.json +# get url for latest alpha linux en_US package +TOR_BROWSER_VERSION=$(grep -Eo ""version":"[0-9.a]+"" downloads.json | grep -Eo "[0-9.a]+") +TOR_BROWSER_PACKAGE="tor-browser-linux64-${TOR_BROWSER_VERSION}_en-US.tar.xz" +TOR_BROWSER_PACKAGE_URL="https://dist.torproject.org/torbrowser/$%7BTOR_BROWSER_VERSION%7D/$%7BTOR_BR..." + +# remove download manifest +rm downloads.json + +# clear out previous tor-browser and previous package +rm -rf "${BINARIES_DIR}/dev" +rm -f "${TOR_BROWSER_PACKAGE}" + +# download +rm -f "${TOR_BROWSER_PACKAGE}" +wget "${TOR_BROWSER_PACKAGE_URL}" +mkdir -p "${BINARIES_DIR}" + +# and extract +tar -xf ${TOR_BROWSER_PACKAGE} -C "${BINARIES_DIR}" +mv "${BINARIES_DIR}/tor-browser_en-US" "${BINARIES_DIR}/dev" + +# cleanup +rm -f "${TOR_BROWSER_PACKAGE}" diff --git a/tools/torbrowser/ide.sh b/tools/torbrowser/ide.sh new file mode 100755 index 000000000000..5da0c670d8c5 --- /dev/null +++ b/tools/torbrowser/ide.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +IDE=$1 +DEV_ROOT=$2 + +cd $DEV_ROOT +./mach ide $IDE diff --git a/tools/torbrowser/jslint.sh b/tools/torbrowser/jslint.sh new file mode 100755 index 000000000000..be1016275c28 --- /dev/null +++ b/tools/torbrowser/jslint.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +DEV_ROOT=$1 +JS_FILE=$2 + +cd $DEV_ROOT +./mach lint -l eslint --fix $JS_FILE
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit c8ec3849ef683a8e0a4de23d9caf24f57576838f 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/base/content/utilityOverlay.js | 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/alpha/branding.nsi | 64 +++++++++++++++ .../en-US/brand.properties => alpha/configure.sh} | 4 +- 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 | 5 ++ .../{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 browser/branding/alpha/locales/en-US/brand.dtd | 11 +++ .../{nightly => alpha}/locales/en-US/brand.ftl | 2 +- .../branding/alpha/locales/en-US/brand.properties | 14 ++++ 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 | 2 +- browser/branding/nightly/locales/en-US/brand.ftl | 2 +- .../nightly/locales/en-US/brand.properties | 6 +- 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 | 13 +++ 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 | 2 +- .../official/locales/en-US/brand.properties | 6 +- 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.css | 4 +- .../shared/identity-block/identity-block.css | 13 +-- .../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 ++++++++- 131 files changed, 877 insertions(+), 90 deletions(-)
diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in index 54d6b43fe126..8dd3a9a65661 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 9ceaf88f15c1..d8858e9f01bf 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/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index 40a1fa59d0e2..977f40e5e331 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -1115,6 +1115,9 @@ function makeURLAbsolute(aBase, aUrl) { }
function getHelpLinkURL(aHelpTopic) { + if (aHelpTopic === "firefox-help" || aHelpTopic === "firefox-osxkey") { + return "about:manual"; + } var url = Services.urlFormatter.formatURLPref("app.support.baseURL"); return url + aHelpTopic; } diff --git a/browser/branding/alpha/VisualElements_150.png b/browser/branding/alpha/VisualElements_150.png new file mode 100644 index 000000000000..fbf4af94d813 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 000000000000..1add6b0e77ff 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 000000000000..0a7e3088f4f0 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 000000000000..3b78c9498c93 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 000000000000..c724d1803c26 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 000000000000..b37853b77643 --- /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/official/locales/en-US/brand.properties b/browser/branding/alpha/configure.sh similarity index 71% copy from browser/branding/official/locales/en-US/brand.properties copy to browser/branding/alpha/configure.sh index e19952690248..243091484f75 100644 --- a/browser/branding/official/locales/en-US/brand.properties +++ b/browser/branding/alpha/configure.sh @@ -2,6 +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/.
-brandShorterName=Firefox -brandShortName=Firefox -brandFullName=Mozilla Firefox +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 000000000000..7d705be61dfd 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 000000000000..caf587e212b6 --- /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 000000000000..193c856f3e8c 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 000000000000..6f71130b417d --- /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 000000000000..3b93625ddd70 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 000000000000..293b5f493f3f --- /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 000000000000..f4b6a3fc41b7 --- /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 000000000000..65270a3cd9a9 --- /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 000000000000..382a061774aa --- /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 77% copy from browser/branding/nightly/content/jar.mn copy to browser/branding/alpha/content/jar.mn index 69e19cf536f3..93ff6ecf736b 100644 --- a/browser/branding/nightly/content/jar.mn +++ b/browser/branding/alpha/content/jar.mn @@ -10,9 +10,14 @@ browser.jar: content/branding/about-logo@2x.png content/branding/about-wordmark.svg content/branding/firefox-wordmark.svg + content/branding/aboutlogins.svg content/branding/icon16.png (../default16.png) content/branding/icon32.png (../default32.png) 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 fff7035065b0..d988c0ff9b16 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 000000000000..aabb6177194b --- /dev/null +++ b/browser/branding/alpha/content/tor-styles.css @@ -0,0 +1,13 @@ +%include ../../tor-styles.inc.css + +/* default theme*/ +:root { + --tor-branding-color: var(--teal-70); +} + +/* dark theme */ +@media (prefers-color-scheme: dark) { + :root { + --tor-branding-color: var(--teal-60); + } +} diff --git a/browser/branding/alpha/default128.png b/browser/branding/alpha/default128.png new file mode 100644 index 000000000000..fbc27b91d118 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 000000000000..3a4e1b679b27 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 000000000000..4feb2dbd400c 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 000000000000..4387f97e3d62 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 000000000000..844f1a0323ee 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 000000000000..679f5f9db43f 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 000000000000..85f0253d88ca 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 000000000000..b12f58b88bb4 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 000000000000..c48f1c5bf4ee 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 000000000000..866d93a43bc8 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 000000000000..7fbfffe2228e 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 000000000000..45aa08bb1658 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 000000000000..6b82c923a662 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 85e09dd7a910..a71938708aff 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 000000000000..baad294f8497 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 000000000000..e25514996d37 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 000000000000..250c7adea0d6 --- /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 000000000000..e25514996d37 Binary files /dev/null and b/browser/branding/alpha/firefox64.ico differ diff --git a/browser/branding/alpha/locales/en-US/brand.dtd b/browser/branding/alpha/locales/en-US/brand.dtd new file mode 100644 index 000000000000..0b15c9978e01 --- /dev/null +++ b/browser/branding/alpha/locales/en-US/brand.dtd @@ -0,0 +1,11 @@ +<!-- 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/. --> + +<!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 "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 f633bc269f58..e970d32cb43e 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/alpha/locales/en-US/brand.properties b/browser/branding/alpha/locales/en-US/brand.properties new file mode 100644 index 000000000000..e96b063b9034 --- /dev/null +++ b/browser/branding/alpha/locales/en-US/brand.properties @@ -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/. + +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=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 c04a7a1cf0f0..d13c2110148f 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 fff7035065b0..d988c0ff9b16 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 fff7035065b0..dd081ac44496 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 000000000000..a9b37c08c6e1 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 000000000000..55372077102c 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 000000000000..47677c13fba6 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 000000000000..792134ab45d7 --- /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 000000000000..891036a4fe35 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 000000000000..8044838c79f5 --- /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 000000000000..2d9c3ad1891e --- /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 000000000000..a754d2db1e11 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 000000000000..c944205be23f 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 000000000000..9e523b5fa196 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 908553b8b95c..95cebf735920 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 fa2191146174..a29d863d1766 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 cefb95b1c3d2..ccd90b8cf748 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 f51ece572b27..243091484f75 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 000000000000..382a061774aa --- /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 69e19cf536f3..2459fa9cc452 100644 --- a/browser/branding/nightly/content/jar.mn +++ b/browser/branding/nightly/content/jar.mn @@ -15,4 +15,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 000000000000..954d719450d6 --- /dev/null +++ b/browser/branding/nightly/content/tor-styles.css @@ -0,0 +1,13 @@ +%include ../../tor-styles.inc.css + +/* default theme*/ +:root { + --tor-branding-color: var(--blue-60); +} + +/* dark theme */ +@media (prefers-color-scheme: dark) { + :root { + --tor-branding-color: var(--blue-40); + } +} diff --git a/browser/branding/nightly/default128.png b/browser/branding/nightly/default128.png index 8fe085c56ffc..12998ed018a7 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 e01114ba2bb5..737ade977a6b 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 0527dfd563cb..02c87a9e2db6 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 019d020fde05..34cfedb2d908 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 d0d8bd01cc1a..f619aecbc6e3 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 c0986eae9367..499bc8ff7fc9 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 1980ffb35c80..fc99e3829d5f 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 000000000000..4ff5f7fa3495 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 551c98d44431..5a84a5384942 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 8cb0f7f9dc32..4acf7a5d1a4b 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 e5d0d840a7b4..ecb8e3dc6c73 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 85e09dd7a910..a71938708aff 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 643ddd4f5812..4b0adc0f5af7 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 240b64298f76..eb28c93ab25f 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 000000000000..c11b568b8553 --- /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 1f50606af6a1..eb28c93ab25f 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 ac881cd35fca..b005cdf5ed0c 100644 --- a/browser/branding/nightly/locales/en-US/brand.dtd +++ b/browser/branding/nightly/locales/en-US/brand.dtd @@ -2,4 +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/. -->
-<!ENTITY brandShortName "Nightly"> +<!ENTITY brandShortName "Tor Browser"> diff --git a/browser/branding/nightly/locales/en-US/brand.ftl b/browser/branding/nightly/locales/en-US/brand.ftl index f633bc269f58..e970d32cb43e 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 e84fdd07d46b..b1e40a5a9f14 100644 --- a/browser/branding/nightly/locales/en-US/brand.properties +++ b/browser/branding/nightly/locales/en-US/brand.properties @@ -2,6 +2,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/.
-brandShorterName=Nightly -brandShortName=Nightly -brandFullName=Firefox Nightly +brandShorterName=Tor Browser +brandShortName=Tor Browser +brandFullName=Tor Browser diff --git a/browser/branding/nightly/locales/jar.mn b/browser/branding/nightly/locales/jar.mn index c04a7a1cf0f0..d13c2110148f 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 fff7035065b0..d988c0ff9b16 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 89eaf901254c..a754d2db1e11 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 451d87c70ef0..c944205be23f 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 f9d6a870e952..9e523b5fa196 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 f764a48966ca..acc02c97d827 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 197a645b4236..890a227e251a 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 1fbe981c9c5a..243091484f75 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 000000000000..382a061774aa --- /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 69e19cf536f3..2459fa9cc452 100644 --- a/browser/branding/official/content/jar.mn +++ b/browser/branding/official/content/jar.mn @@ -15,4 +15,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 000000000000..4ae0e4efe3ea --- /dev/null +++ b/browser/branding/official/content/tor-styles.css @@ -0,0 +1,13 @@ +%include ../../tor-styles.inc.css + +/* default theme*/ +:root { + --tor-branding-color: var(--purple-60); +} + +/* dark theme */ +@media (prefers-color-scheme: dark) { + :root { + --tor-branding-color: var(--purple-30); + } +} diff --git a/browser/branding/official/default128.png b/browser/branding/official/default128.png index b92d78ca6d09..18f3572d0d79 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 fe860e46b1e7..904b84e49871 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 3aff987a8476..41cc3543d39f 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 cfce6e7d64fd..195cae90d3ed 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 ddc9d4db1f14..809dbad4ab16 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 67042dbb2b4a..e8e68eb4492c 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 765ea42459d3..e839211d260b 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 000000000000..23942859673d 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 39e77389022c..147a229fab8b 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 4353ef0965f3..3e2c44f187ce 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 50d9701405a5..27a776a12557 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 fcec7dc15646..3e5d99012f89 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 85e09dd7a910..3b2f265df644 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 3cc884734c9d..b9874461e519 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 d8ba663ba76e..db0a9af865b6 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 000000000000..9240dc6e84ca --- /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 c3a32449d27a..db0a9af865b6 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 b5474b4c99e9..b005cdf5ed0c 100644 --- a/browser/branding/official/locales/en-US/brand.dtd +++ b/browser/branding/official/locales/en-US/brand.dtd @@ -2,4 +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/. -->
-<!ENTITY brandShortName "Firefox"> +<!ENTITY brandShortName "Tor Browser"> diff --git a/browser/branding/official/locales/en-US/brand.properties b/browser/branding/official/locales/en-US/brand.properties index e19952690248..b1e40a5a9f14 100644 --- a/browser/branding/official/locales/en-US/brand.properties +++ b/browser/branding/official/locales/en-US/brand.properties @@ -2,6 +2,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/.
-brandShorterName=Firefox -brandShortName=Firefox -brandFullName=Mozilla Firefox +brandShorterName=Tor Browser +brandShortName=Tor Browser +brandFullName=Tor Browser diff --git a/browser/branding/official/wizHeader.bmp b/browser/branding/official/wizHeader.bmp index 420824226dfe..a754d2db1e11 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 7f74929910bd..c944205be23f 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 b3b3c91d327c..9e523b5fa196 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 000000000000..55dc9b6238b3 --- /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 0b97e8c3cb59..65cf06fcf5c1 100644 --- a/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js @@ -244,10 +244,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 => { e.preventDefault(); diff --git a/browser/themes/shared/controlcenter/panel.css b/browser/themes/shared/controlcenter/panel.css index e1d3d6634b28..4a2c291c70c4 100644 --- a/browser/themes/shared/controlcenter/panel.css +++ b/browser/themes/shared/controlcenter/panel.css @@ -10,7 +10,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; @@ -401,7 +401,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.css b/browser/themes/shared/identity-block/identity-block.css index c30d70b8f935..63c07d21a453 100644 --- a/browser/themes/shared/identity-block/identity-block.css +++ b/browser/themes/shared/identity-block/identity-block.css @@ -53,6 +53,10 @@ border-radius: var(--urlbar-icon-border-radius); }
+#identity-box[pageproxystate="valid"].chromeUI #identity-icon-label { + color: var(--tor-branding-color); +} + #identity-icon-label { padding-inline-start: 4px; } @@ -147,12 +151,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 000000000000..93eb24b03905 --- /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 000000000000..f920a93ac410 --- /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 000000000000..7655a800d9ee --- /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 ae8d82c09a88..d04f95e59ea0 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -23,6 +23,8 @@ skin/classic/browser/menupanel.css (../shared/menupanel.css) skin/classic/browser/notification-icons.css (../shared/notification-icons.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/searchbar.css (../shared/searchbar.css) skin/classic/browser/setDesktopBackground.css (../shared/setDesktopBackground.css) skin/classic/browser/sidebar.css (../shared/sidebar.css) @@ -67,6 +69,9 @@ skin/classic/browser/downloads/progressmeter.css (../shared/downloads/progressmeter.css) skin/classic/browser/drm-icon.svg (../shared/drm-icon.svg) skin/classic/browser/identity-block/identity-block.css (../shared/identity-block/identity-block.css) + 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/permissions.svg (../shared/identity-block/permissions.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 000000000000..1852350d57f7 --- /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 000000000000..de57b6ee301a --- /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 000000000000..e2937b175341 --- /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 d4c0cdace9fe..9240dc6e84ca 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 8ece78c5c1cd..9240dc6e84ca 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 fe4d116b1660..d7895f1107c5 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 dbc7b084d6c0..9240dc6e84ca 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 4c195cf17c85..9240dc6e84ca 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
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit f14573b550ba5738f69e57b92ff18442381b01c0 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 | 3 +- .../extensions/onboarding/OnboardingTelemetry.jsm | 610 +++++++ .../extensions/onboarding/OnboardingTourType.jsm | 56 + browser/extensions/onboarding/README.md | 87 + browser/extensions/onboarding/api.js | 260 +++ browser/extensions/onboarding/background.js | 8 + .../extensions/onboarding/content/Onboarding.jsm | 1873 ++++++++++++++++++++ .../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 | 114 ++ .../extensions/onboarding/content/onboarding.css | 589 ++++++ .../extensions/onboarding/content/onboarding.js | 49 + 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} | 8 +- .../extensions/{ => onboarding/locales}/moz.build | 3 +- browser/extensions/onboarding/manifest.json | 26 + browser/extensions/onboarding/moz.build | 26 + browser/extensions/onboarding/schema.json | 1 + .../onboarding/test/browser/.eslintrc.js | 5 + .../extensions/onboarding/test/browser/browser.ini | 18 + .../browser/browser_onboarding_accessibility.js | 121 ++ .../test/browser/browser_onboarding_keyboard.js | 205 +++ .../browser/browser_onboarding_notification.js | 79 + .../browser/browser_onboarding_notification_2.js | 114 ++ .../browser/browser_onboarding_notification_3.js | 135 ++ .../browser/browser_onboarding_notification_4.js | 114 ++ .../browser/browser_onboarding_notification_5.js | 32 + ...arding_notification_click_auto_complete_tour.js | 62 + .../browser_onboarding_select_default_tour.js | 112 ++ .../test/browser/browser_onboarding_skip_tour.js | 65 + .../test/browser/browser_onboarding_tours.js | 163 ++ .../test/browser/browser_onboarding_tourset.js | 102 ++ .../test/browser/browser_onboarding_uitour.js | 247 +++ browser/extensions/onboarding/test/browser/head.js | 387 ++++ .../extensions/onboarding/test/unit/.eslintrc.js | 5 + browser/extensions/onboarding/test/unit/head.js | 58 + .../test/unit/test-onboarding-tour-type.js | 155 ++ .../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, 7730 insertions(+), 20 deletions(-)
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 16060fa72eb0..70e71c0b993e 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -2156,6 +2156,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 b95b30bedbe8..99b289bd934a 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -3541,17 +3541,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 6fdb9b93879e..0395969faa96 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -821,6 +821,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. @@ -1408,6 +1416,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) { @@ -1454,6 +1487,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(); } @@ -1532,6 +1568,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 2df11e89dd48..39bbc2937271 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -4,5 +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 += [ -] +DIRS += ["onboarding"] diff --git a/browser/extensions/onboarding/OnboardingTelemetry.jsm b/browser/extensions/onboarding/OnboardingTelemetry.jsm new file mode 100644 index 000000000000..96e07f58de82 --- /dev/null +++ b/browser/extensions/onboarding/OnboardingTelemetry.jsm @@ -0,0 +1,610 @@ +/* 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; +} + +// 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 000000000000..0d005a36eb96 --- /dev/null +++ b/browser/extensions/onboarding/OnboardingTourType.jsm @@ -0,0 +1,56 @@ +/* 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 000000000000..c63be42b7181 --- /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 000000000000..f514530ea6e8 --- /dev/null +++ b/browser/extensions/onboarding/api.js @@ -0,0 +1,260 @@ +/* -*- 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 000000000000..efe296ff2278 --- /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 000000000000..ad40b8dac8d9 --- /dev/null +++ b/browser/extensions/onboarding/content/Onboarding.jsm @@ -0,0 +1,1873 @@ +/* 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) { + // 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) { + 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 && this._isTimeForNextTourNotification(lastTime)) { + queue.shift(); + } + // We don't want to prompt the completed tour. + while (queue.length && this.isTourCompleted(queue[0])) { + queue.shift(); + } + + if (!queue.length) { + 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 000000000000..b5f056737f11 --- /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 000000000000..0c0cb30df5dc --- /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 000000000000..c52e4b8500f7 --- /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 000000000000..aad20181b996 --- /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 000000000000..f7c5c219aada --- /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 000000000000..f90163e4b4d7 --- /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 000000000000..f4930d09f7af --- /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 000000000000..9be029397ccf --- /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 000000000000..74562d37236d --- /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 000000000000..6b27dea39252 --- /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 000000000000..ae0a9409fa5c --- /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 000000000000..235f7d65b685 --- /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 000000000000..064c2e619486 --- /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 000000000000..ad23ba27400c --- /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 000000000000..7d4d2c416801 --- /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 000000000000..8d219dce78b5 --- /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 000000000000..3e06a3852288 --- /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 000000000000..286422275aa7 --- /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 000000000000..173e72c332df --- /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 000000000000..c9345ed2ba1d --- /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 000000000000..6a8729197f0f --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding-tour-agent.js @@ -0,0 +1,114 @@ +/* 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 000000000000..8f2431477634 --- /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 000000000000..7518a1ab6631 --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding.js @@ -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/. */ + +/* 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 000000000000..3fc5ffa41b86 --- /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 000000000000..1d580be9861f --- /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 000000000000..cc545222a107 --- /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 2df11e89dd48..0801f8512775 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/onboarding/locales/jar.mn @@ -1,8 +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 += [ -] +[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 91% copy from browser/extensions/moz.build copy to browser/extensions/onboarding/locales/moz.build index 2df11e89dd48..d988c0ff9b16 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/onboarding/locales/moz.build @@ -4,5 +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 += [ -] +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/extensions/onboarding/manifest.json b/browser/extensions/onboarding/manifest.json new file mode 100644 index 000000000000..fcf46b444c9b --- /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 000000000000..4756afe507fb --- /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 000000000000..fe51488c7066 --- /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 000000000000..1779fd7f1cf8 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/.eslintrc.js @@ -0,0 +1,5 @@ +"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 000000000000..abc2e915d551 --- /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 000000000000..9f2f1c7c9ce9 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js @@ -0,0 +1,121 @@ +/* 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 000000000000..a67814bdae39 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js @@ -0,0 +1,205 @@ +/* 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 000000000000..bb0d3d4f2479 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js @@ -0,0 +1,79 @@ +/* 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 000000000000..0e517f6688de --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js @@ -0,0 +1,114 @@ +/* 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 000000000000..f9f435e5e554 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js @@ -0,0 +1,135 @@ +/* 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 000000000000..41f42deec973 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js @@ -0,0 +1,114 @@ +/* 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 000000000000..b7e2aa477539 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js @@ -0,0 +1,32 @@ +/* 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 000000000000..cde56950b51f --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js @@ -0,0 +1,62 @@ +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 000000000000..8d7033b01d0f --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js @@ -0,0 +1,112 @@ +/* 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 000000000000..7aec03c34c2b --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js @@ -0,0 +1,65 @@ +/* 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 000000000000..db42e6c60637 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js @@ -0,0 +1,163 @@ +/* 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 000000000000..f54727d624e4 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js @@ -0,0 +1,102 @@ +/* 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 000000000000..a4208e749390 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js @@ -0,0 +1,247 @@ +/* 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 000000000000..4a151cc97192 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/head.js @@ -0,0 +1,387 @@ +/* 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 000000000000..69e89d0054ba --- /dev/null +++ b/browser/extensions/onboarding/test/unit/.eslintrc.js @@ -0,0 +1,5 @@ +"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 000000000000..45c603e34dd1 --- /dev/null +++ b/browser/extensions/onboarding/test/unit/head.js @@ -0,0 +1,58 @@ +/** + * 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 000000000000..83804c7ee719 --- /dev/null +++ b/browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js @@ -0,0 +1,155 @@ +/* + * 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 000000000000..ed484d0f200f --- /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 9b5a82add399..ee46e9b7d30d 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -243,6 +243,7 @@ @RESPATH@/browser/chrome/icons/default/default64.png @RESPATH@/browser/chrome/icons/default/default128.png #endif +@RESPATH@/browser/features/*
; Base Browser @RESPATH@/browser/chrome/newidentity.manifest diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index 07e699675878..2cb5e78a1a2f 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -52,6 +52,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) @@ -65,6 +66,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 22eb5cbdb177..504c498debf5 100644 --- a/browser/locales/filter.py +++ b/browser/locales/filter.py @@ -17,6 +17,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 7a6599740b20..c70485a63d53 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 e9d50107cb10..0fdbfa131898 100644 --- a/browser/locales/l10n.toml +++ b/browser/locales/l10n.toml @@ -133,6 +133,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 4b291e99ccc2..4c959490b44b 100644 --- a/extensions/permissions/PermissionManager.cpp +++ b/extensions/permissions/PermissionManager.cpp @@ -127,7 +127,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 fa9c940c7052..fab4ebf22899 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 6975fd72b9c76de7e3cd0d2f3857ff2eb3cd0282 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 | 11 +- 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 | 90 +- browser/components/uitour/UITourChild.jsm | 33 +- browser/extensions/onboarding/api.js | 95 +- .../extensions/onboarding/content/Onboarding.jsm | 1035 ++++++++++---------- .../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 | 324 ++++++ .../onboarding/content/onboarding-tour-agent.js | 189 ++-- .../extensions/onboarding/content/onboarding.css | 165 +++- .../extensions/onboarding/content/onboarding.js | 7 +- browser/extensions/onboarding/jar.mn | 9 +- browser/extensions/onboarding/moz.build | 5 +- browser/themes/linux/browser.css | 9 - browser/themes/shared/UITour.css | 56 +- browser/themes/windows/browser.css | 9 - intl/strres/nsStringBundle.cpp | 1 + 50 files changed, 1276 insertions(+), 2237 deletions(-)
diff --git a/browser/app/permissions b/browser/app/permissions index 965912204e70..d8439d49346b 100644 --- a/browser/app/permissions +++ b/browser/app/permissions @@ -8,14 +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 https://truecolors.firefox.com -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 6e443412ab34..6e093f919c29 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -358,6 +358,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 f44d1635165e..029a63bea57c 100644 --- a/browser/base/content/main-popupset.inc.xhtml +++ b/browser/base/content/main-popupset.inc.xhtml @@ -253,6 +253,7 @@ <toolbarbutton id="UITourTooltipClose" class="close-icon" data-l10n-id="ui-tour-info-panel-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 2eaf55caf233..c721656d6016 100644 --- a/browser/components/uitour/UITour-lib.js +++ b/browser/components/uitour/UITour-lib.js @@ -820,6 +820,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 0395969faa96..80c02481572e 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -31,6 +31,26 @@ XPCOMUtils.defineLazyModuleGetters(this, { // 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", @@ -81,6 +101,27 @@ var UITour = {
highlightEffects: ["random", "wobble", "zoom", "color", "focus-outline"], 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", { @@ -277,6 +318,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; @@ -615,6 +661,16 @@ 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 @@ -857,10 +913,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); @@ -909,7 +962,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" @@ -1376,6 +1432,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) { @@ -1440,7 +1500,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) { @@ -1475,6 +1535,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()) { @@ -2019,6 +2083,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 e2e763c8f4c1..02caae849de8 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 f514530ea6e8..aa583cb11d8e 100644 --- a/browser/extensions/onboarding/api.js +++ b/browser/extensions/onboarding/api.js @@ -8,23 +8,23 @@ 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" -); +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 {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], @@ -36,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", @@ -45,12 +58,8 @@ const PREF_WHITELIST = [ "onboarding-tour-screenshots", "onboarding-tour-singlesearch", "onboarding-tour-sync", -].forEach(tourId => - PREF_WHITELIST.push([ - `browser.onboarding.tour.${tourId}.completed`, - PREF_BOOL, - ]) -); +#endif +].forEach(tourId => PREF_WHITELIST.push([`browser.onboarding.tour.${tourId}.completed`, PREF_BOOL]));
let waitingForBrowserReady = true; let startupData; @@ -67,7 +76,7 @@ let startupData; **/ function setPrefs(prefs) { prefs.forEach(pref => { - let prefObj = PREF_WHITELIST.find(([name]) => name == pref.name); + let prefObj = PREF_WHITELIST.find(([name ]) => name == pref.name); if (!prefObj) { return; } @@ -85,13 +94,26 @@ function setPrefs(prefs) { Services.prefs.setStringPref(name, pref.value); break; default: - throw new TypeError( - `Unexpected type (${type}) for preference ${name}.` - ); + throw new TypeError(`Unexpected type (${type}) for preference ${name}.`); } }); }
+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. @@ -135,10 +157,7 @@ let syncTourChecker = {
setComplete() { this._loggedIn = true; - Services.prefs.setBoolPref( - "browser.onboarding.tour.onboarding-tour-sync.completed", - true - ); + Services.prefs.setBoolPref("browser.onboarding.tour.onboarding-tour-sync.completed", true); },
unregister() { @@ -164,13 +183,15 @@ function initContentMessageListener() { setPrefs(msg.data.params); break; case "get-login-status": - msg.target.messageManager.sendAsyncMessage( - "Onboarding:ResponseLoginStatus", - { - isLoggedIn: syncTourChecker.isLoggedIn(), - } - ); + msg.target.messageManager.sendAsyncMessage("Onboarding:ResponseLoginStatus", { + 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); @@ -178,6 +199,7 @@ function initContentMessageListener() { Cu.reportError(e); } break; +#endif } }); } @@ -189,7 +211,6 @@ function onBrowserReady() { waitingForBrowserReady = false;
OnboardingTourType.check(); - OnboardingTelemetry.init(startupData); Services.mm.loadFrameScript("resource://onboarding/onboarding.js", true); initContentMessageListener(); } @@ -216,16 +237,12 @@ function observe(subject, topic, data) {
this.onboarding = class extends ExtensionAPI { onStartup() { - resProto.setSubstitutionWithFlags( - RESOURCE_HOST, + resProto.setSubstitutionWithFlags(RESOURCE_HOST, Services.io.newURI("chrome/content/", null, this.extension.rootURI), - resProto.ALLOW_CONTENT_ACCESS - ); + resProto.ALLOW_CONTENT_ACCESS);
if (this.extension.rootURI instanceof Ci.nsIJARURI) { - this.manifest = this.extension.rootURI.JARFile.QueryInterface( - Ci.nsIFileURL - ).file; + this.manifest = this.extension.rootURI.JARFile.QueryInterface(Ci.nsIFileURL).file; } else if (this.extension.rootURI instanceof Ci.nsIFileURL) { this.manifest = this.extension.rootURI.file; } @@ -233,9 +250,7 @@ this.onboarding = class extends ExtensionAPI { if (this.manifest) { Components.manager.addBootstrappedManifestLocation(this.manifest); } else { - Cu.reportError( - "Cannot find onboarding chrome.manifest for registring translated strings" - ); + Cu.reportError("Cannot find onboarding chrome.manifest for registring translated strings"); }
// Only start Onboarding when the browser UI is ready diff --git a/browser/extensions/onboarding/content/Onboarding.jsm b/browser/extensions/onboarding/content/Onboarding.jsm index ad40b8dac8d9..3a47f366df49 100644 --- a/browser/extensions/onboarding/content/Onboarding.jsm +++ b/browser/extensions/onboarding/content/Onboarding.jsm @@ -11,20 +11,22 @@ 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 - .createBundle("chrome://branding/locale/brand.properties") - .GetStringFromName("brandShortName"); + .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 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 SPEECH_BUBBLE_UPDATETOUR_STRING_ID = "onboarding.overlay-icon-tooltip-updated2"; const ICON_STATE_WATERMARK = "watermark"; const ICON_STATE_DEFAULT = "default";
@@ -68,12 +70,7 @@ function createOnboardingTourContent(div, imageSrc) { /** * Helper function to create the tour button UI element. */ -function createOnboardingTourButton( - div, - buttonId, - l10nId, - buttonElementTagName = "button" -) { +function createOnboardingTourButton(div, buttonId, l10nId, buttonElementTagName = "button") { let doc = div.ownerDocument; let aside = doc.createElement("aside"); aside.className = "onboarding-tour-button-container"; @@ -88,6 +85,194 @@ function createOnboardingTourButton( 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 @@ -110,162 +295,93 @@ function createOnboardingTourButton( * }, **/ var onboardingTourset = { - private: { + "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" - ), + 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" - ); + 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: { + "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 - ), + 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" - ); + 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: { + "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 - ), + 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" - ); + 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: { + "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 - ), + 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 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"; @@ -289,25 +405,19 @@ var onboardingTourset = { isDefaultBrowserMsg.append(isDefault2ndMessage);
div.addEventListener("beforeshow", () => { - win.document.dispatchEvent( - new Event("Agent:CanSetDefaultBrowserInBackground") - ); + win.document.dispatchEvent(new Event("Agent:CanSetDefaultBrowserInBackground")); }); return div; }, }, - sync: { + "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" - ), + title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.title"), + message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.message"), button: bundle.GetStringFromName("onboarding.button.learnMore"), }; }, @@ -320,31 +430,21 @@ var onboardingTourset = { // 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 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" - ); + 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.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.setAttribute("data-l10n-id", "onboarding.tour-sync.logged-in.description"); pLoggedIn.className = "show-on-logged-in"; description.appendChild(pLoggedIn);
@@ -368,9 +468,8 @@ var onboardingTourset = { 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.placeholder = + bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder"); input.pattern = emailRegex; form.appendChild(input);
@@ -395,150 +494,93 @@ var onboardingTourset = { 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" - ); + 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; + 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 - ); + this.mm.addMessageListener("Onboarding:ResponseLoginStatus", loginStatusListener); });
return div; }, }, - library: { + "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 - ), + 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" - ); + 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: { + "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" - ), + 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" - ); + 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: { + "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 - ), + 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" - ); + createOnboardingTourDescription(div, + "onboarding.tour-performance.title", "onboarding.tour-performance.description"); + createOnboardingTourContent(div, "resource://onboarding/img/figure_performance.svg");
return div; }, }, - screenshots: { + "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 - ), + 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"), }; }, @@ -547,22 +589,14 @@ var onboardingTourset = { // 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" - ); + 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"); @@ -572,6 +606,7 @@ var onboardingTourset = { }, }, }; +#endif
/** * The script won't be initialized if we turned off onboarding by @@ -583,14 +618,14 @@ class Onboarding { 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, + action, params, }); }
@@ -599,15 +634,13 @@ class Onboarding { * @param {Object} data the payload for the telemetry */ telemetry(data) { - this.sendMessageToChrome("ping-centre", { data }); + this.sendMessageToChrome("ping-centre", {data}); }
registerNewTelemetrySession(data) { - this.telemetry( - Object.assign(data, { - type: "onboarding-register-session", - }) - ); + this.telemetry(Object.assign(data, { + type: "onboarding-register-session", + })); }
async init(contentWindow) { @@ -616,10 +649,7 @@ class Onboarding { // 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" - ); + this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update");
let tourIds = this._getTourIDList(); tourIds.forEach(tourId => { @@ -635,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);
@@ -667,9 +700,8 @@ class Onboarding { this._window.addEventListener("beforeunload", this); this._window.addEventListener("unload", this); this._window.addEventListener("resize", this); - this._resizeTimerId = this._window.requestIdleCallback(() => - this._resizeUI() - ); + this._resizeTimerId = + this._window.requestIdleCallback(() => this._resizeUI()); // start log the onboarding-session when the tab is visible this.telemetry({ type: "onboarding-session-begin", @@ -678,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(); @@ -686,14 +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() { @@ -708,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); @@ -719,34 +762,21 @@ class Onboarding { this._loadJS(TOUR_AGENT_JS_URI);
this._initPrefObserver(); - this._onIconStateChange( - Services.prefs.getStringPref( - "browser.onboarding.state", - ICON_STATE_DEFAULT - ) - ); + 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() { - 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()); + 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() { @@ -756,22 +786,14 @@ class Onboarding {
this._prefsObserved = new Map(); this._prefsObserved.set("browser.onboarding.state", () => { - this._onIconStateChange( - Services.prefs.getStringPref( - "browser.onboarding.state", - ICON_STATE_DEFAULT - ) - ); + 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(); - } - ); + 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); @@ -781,12 +803,10 @@ class Onboarding { _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, - }, - ]); + this.sendMessageToChrome("set-prefs", [{ + name: "browser.onboarding.state", + value: ICON_STATE_WATERMARK, + }]); } }
@@ -804,9 +824,8 @@ class Onboarding { * 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 this._tours.find(tour => !this.isTourCompleted(tour.id)) || + this._tours[0]; }
/* @@ -818,9 +837,7 @@ class Onboarding { return ""; }
- let tourItem = this._tourItems.find(item => - item.classList.contains("onboarding-active") - ); + let tourItem = this._tourItems.find(item => item.classList.contains("onboarding-active")); return tourItem ? tourItem.id : ""; }
@@ -828,9 +845,8 @@ class Onboarding { * Return current logo state as "logo" or "watermark". */ get _logoState() { - return this._overlayIcon.classList.contains("onboarding-watermark") - ? "watermark" - : "logo"; + return this._overlayIcon.classList.contains("onboarding-watermark") ? + "watermark" : "logo"; }
/** @@ -840,9 +856,7 @@ class Onboarding { let state; if (this._overlayIcon.classList.contains("onboarding-watermark")) { state = "hide"; - } else if ( - this._overlayIcon.classList.contains("onboarding-speech-bubble") - ) { + } else if (this._overlayIcon.classList.contains("onboarding-speech-bubble")) { state = "bubble"; } else { state = "dot"; @@ -894,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-5/"; + 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(); @@ -918,10 +943,8 @@ class Onboarding { // 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"; + let eventName = id === "onboarding-overlay-close-btn" ? + "overlay-close-button-click" : "overlay-close-outside-click"; this.telemetry({ type: eventName, current_tour_id: this._activeTourId, @@ -965,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({ @@ -978,9 +1031,10 @@ 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.setToursCompleted([ activeTourId ]); this.telemetry({ type: "overlay-cta-click", current_tour_id: activeTourId, @@ -991,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 @@ -1005,11 +1074,8 @@ class Onboarding { * @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 elms = [...this._dialog.querySelectorAll( + `button, input[type="checkbox"], input[type="email"], [tabindex="0"]`)]; let next; if (back) { if (elms.indexOf(current) === 0) { @@ -1125,9 +1191,8 @@ class Onboarding { break; case "resize": this._window.cancelIdleCallback(this._resizeTimerId); - this._resizeTimerId = this._window.requestIdleCallback(() => - this._resizeUI() - ); + this._resizeTimerId = + this._window.requestIdleCallback(() => this._resizeUI()); break; case "keydown": this.handleKeydown(evt); @@ -1149,12 +1214,12 @@ class Onboarding { } this.uiInitialized = false;
- this._overlayIcon.dispatchEvent( - new this._window.CustomEvent("Agent:Destroy") - ); + 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(); @@ -1165,7 +1230,8 @@ class Onboarding { this.hideNotification(); this._notificationBar.remove(); } - this._tourItems = this._tourPages = this._overlayIcon = this._overlay = this._notificationBar = null; + this._tourItems = this._tourPages = + this._overlayIcon = this._overlay = this._notificationBar = null; }
_onIconStateChange(state) { @@ -1177,19 +1243,28 @@ 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) { + if (this._tourItems.length == 0) { // Lazy loading until first toggle. this._loadTours(this._tours); }
- if ( - this._overlay && - !this._overlay.classList.contains("onboarding-opened") - ) { + if (this._overlay && !this._overlay.classList.contains("onboarding-opened")) { this.hideNotification(); this._overlay.classList.add("onboarding-opened"); this.toggleModal(true); @@ -1201,10 +1276,7 @@ class Onboarding { }
hideOverlay() { - if ( - this._overlay && - this._overlay.classList.contains("onboarding-opened") - ) { + if (this._overlay && this._overlay.classList.contains("onboarding-opened")) { this._overlay.classList.remove("onboarding-opened"); this.toggleModal(false); this.telemetry({ @@ -1223,10 +1295,8 @@ class Onboarding { 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) - ); + 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) { @@ -1238,9 +1308,8 @@ class Onboarding { } } else { // Remove all set aria-hidden attributes. - [...doc.body.children].forEach(child => - child.removeAttribute("aria-hidden") - ); + [...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) { @@ -1289,10 +1358,7 @@ class Onboarding { }
isTourCompleted(tourId) { - return Services.prefs.getBoolPref( - `browser.onboarding.tour.${tourId}.completed`, - false - ); + return Services.prefs.getBoolPref(`browser.onboarding.tour.${tourId}.completed`, false); }
setToursCompleted(tourIds) { @@ -1305,7 +1371,7 @@ class Onboarding { }); } }); - if (params.length) { + if (params.length > 0) { this.sendMessageToChrome("set-prefs", params); } } @@ -1328,10 +1394,8 @@ class Onboarding { if (!completedText) { completedText = this._window.document.createElement("span"); completedText.id = completedTextId; - completedText.setAttribute( - "aria-label", - this._bundle.GetStringFromName("onboarding.complete") - ); + completedText.setAttribute("aria-label", + this._bundle.GetStringFromName("onboarding.complete")); targetItem.appendChild(completedText); targetItem.setAttribute("aria-describedby", completedTextId); } @@ -1355,20 +1419,12 @@ class Onboarding { 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" - ) - ) { + 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 - ) { + if (Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms") === 0) { this._firstSession = false; }
@@ -1376,13 +1432,7 @@ class Onboarding { }
_getLastTourChangeTime() { - return ( - 1000 * - Services.prefs.getIntPref( - "browser.onboarding.notification.last-time-of-changing-tour-sec", - 0 - ) - ); + return 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0); }
_muteNotificationOnFirstSession(lastTourChangeTime) { @@ -1391,32 +1441,23 @@ class Onboarding { }
if (lastTourChangeTime <= 0) { - this.sendMessageToChrome("set-prefs", [ - { - name: - "browser.onboarding.notification.last-time-of-changing-tour-sec", - value: Math.floor(Date.now() / 1000), - }, - ]); + 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" - ); + 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" - ); + 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" - ); + let maxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-per-tour-ms"); if (lastTourChangeTime && Date.now() - lastTourChangeTime >= maxTime) { return true; } @@ -1444,14 +1485,8 @@ class Onboarding {
_getNotificationQueue() { let queue = ""; - if ( - Services.prefs.prefHasUserValue( - "browser.onboarding.notification.tour-ids-queue" - ) - ) { - queue = Services.prefs.getStringPref( - "browser.onboarding.notification.tour-ids-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) @@ -1462,12 +1497,10 @@ class Onboarding { // 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, - }, - ]); + this.sendMessageToChrome("set-prefs", [{ + name: "browser.onboarding.notification.tour-ids-queue", + value: queue, + }]); } return queue ? queue.split(",") : []; } @@ -1485,11 +1518,10 @@ 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" - ); + 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 @@ -1498,15 +1530,15 @@ class Onboarding {
let startQueueLength = queue.length; // See if need to move on to the next tour - if (queue.length && this._isTimeForNextTourNotification(lastTime)) { + if (queue.length > 0 && this._isTimeForNextTourNotification(lastTime)) { queue.shift(); } // We don't want to prompt the completed tour. - while (queue.length && this.isTourCompleted(queue[0])) { + while (queue.length > 0 && this.isTourCompleted(queue[0])) { queue.shift(); }
- if (!queue.length) { + if (queue.length == 0) { this.sendMessageToChrome("set-prefs", [ { name: NOTIFICATION_FINISHED_PREF, @@ -1531,17 +1563,11 @@ class Onboarding { 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" - ); + let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn"); actionBtn.textContent = notificationStrings.button; - let tourTitle = this._notificationBar.querySelector( - "#onboarding-notification-tour-title" - ); + let tourTitle = this._notificationBar.querySelector("#onboarding-notification-tour-title"); tourTitle.textContent = notificationStrings.title; - let tourMessage = this._notificationBar.querySelector( - "#onboarding-notification-tour-message" - ); + 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); @@ -1604,10 +1630,7 @@ class Onboarding { 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" - ); + footer.setAttribute("aria-labelledby", "onboarding-notification-tour-title");
let section = this._window.document.createElement("section"); section.id = "onboarding-notification-message-section"; @@ -1642,12 +1665,8 @@ class Onboarding { closeButton.className = "onboarding-close-btn"; footer.appendChild(closeButton);
- closeButton.setAttribute( - "title", - this._bundle.GetStringFromName( - "onboarding.notification-close-button-tooltip" - ) - ); + closeButton.setAttribute("title", + this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip"));
return footer; } @@ -1685,9 +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"); @@ -1705,25 +1723,16 @@ class Onboarding { 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") - ); + 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 - ) - ) { + 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" - ); + skipButton.textContent = this._bundle.GetStringFromName("onboarding.skip-tour-button-label"); footer.appendChild(skipButton); }
@@ -1744,23 +1753,12 @@ class Onboarding { } let tooltip = ""; try { - let tooltipStringId = Services.prefs.getStringPref( - tooltipStringPrefId, - defaultTourStringId - ); - tooltip = this._bundle.formatStringFromName( - tooltipStringId, - [BRAND_SHORT_NAME], - 1 - ); + 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 - ); + tooltip = this._bundle.formatStringFromName(defaultTourStringId, [BRAND_SHORT_NAME], 1); } button.setAttribute("aria-label", tooltip); button.id = "onboarding-overlay-button"; @@ -1769,19 +1767,9 @@ class Onboarding { 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" - ); + 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; }
@@ -1811,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); @@ -1824,10 +1822,7 @@ class Onboarding { // 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 - ); + element.dataset.l10nId, [BRAND_SHORT_NAME], 1); }
div.id = tourPanelId; @@ -1871,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 000000000000..8a637de879ec 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 b5f056737f11..000000000000 --- 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 0c0cb30df5dc..000000000000 --- 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 c52e4b8500f7..000000000000 --- 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 aad20181b996..000000000000 --- 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 f7c5c219aada..000000000000 --- 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 f90163e4b4d7..000000000000 --- 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 f4930d09f7af..000000000000 --- 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 9be029397ccf..000000000000 --- 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 74562d37236d..000000000000 --- 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 000000000000..ea6ecb7f82a3 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 000000000000..36970bd711a8 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 000000000000..87829397ab2a 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 000000000000..018345e4b3a0 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 000000000000..38201ca5c878 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 000000000000..9a5c221c8d8e 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 000000000000..6eb7e5a9995c 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 000000000000..6d8651e58c17 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 000000000000..1bf27c5b9311 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 6b27dea39252..000000000000 --- 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 ae0a9409fa5c..000000000000 --- 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 235f7d65b685..000000000000 --- 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 064c2e619486..000000000000 --- 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 000000000000..21aae225793b 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 ad23ba27400c..000000000000 --- 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 7d4d2c416801..000000000000 --- 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 8d219dce78b5..000000000000 --- 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 3e06a3852288..000000000000 --- 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 286422275aa7..000000000000 --- 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 000000000000..8802bf083ed3 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 173e72c332df..761c31cbf9d0 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 c9345ed2ba1d..000000000000 --- 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 000000000000..714da45227f8 --- /dev/null +++ b/browser/extensions/onboarding/content/onboarding-tor-circuit-display.js @@ -0,0 +1,324 @@ +// 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() { + showCircuitDiagram(); + }, + }); + let options = { + closeButtonCallback() { + 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() { + showNewCircuitButton(); + }, + }); + let options = { + closeButtonCallback() { + 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() { + cleanUp(); + }, + }); + let options = { + closeButtonCallback() { + 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 6a8729197f0f..c12419e205ae 100644 --- a/browser/extensions/onboarding/content/onboarding-tour-agent.js +++ b/browser/extensions/onboarding/content/onboarding-tour-agent.js @@ -5,110 +5,103 @@ /* globals Mozilla */
(function() { - "use strict"; +"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 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; +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("connection"); + 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; + 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"); } - }); - 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); + } else { + btn.disabled = true; + Mozilla.UITour.setConfiguration("defaultBrowser"); } - 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. - } - }; + }); + 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; +#endif + } + 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") && +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 - ); + (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 index 8f2431477634..e801790bf5a8 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 7518a1ab6631..2a92226b709b 100644 --- a/browser/extensions/onboarding/content/onboarding.js +++ b/browser/extensions/onboarding/content/onboarding.js @@ -16,6 +16,7 @@ ChromeUtils.defineModuleGetter( 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)) { @@ -28,11 +29,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 1d580be9861f..af83e1d06e6c 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 4756afe507fb..a5a4b99a4712 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 ed31817d9e2b..a7d925011cf8 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -253,15 +253,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; -} - #context-navigation > .menuitem-iconic > .menu-iconic-left { /* override toolkit/themes/linux/global/menu.css */ padding-inline-end: 0 !important; diff --git a/browser/themes/shared/UITour.css b/browser/themes/shared/UITour.css index 3380e7d4a0bb..ed963acc70a3 100644 --- a/browser/themes/shared/UITour.css +++ b/browser/themes/shared/UITour.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 fe2eaa0ef907..119876ec3150 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -465,15 +465,6 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
/* End private browsing and accessibility indicators */
-#UITourTooltipButtons { - /** - * Override the --arrowpanel-padding so the background extends - * to the sides and bottom of the panel. - */ - margin-inline: -10px; - margin-bottom: -10px; -} - /* Make menu items larger when opened through touch. */ #widget-overflow[touchmode] .toolbarbutton-1, panel[touchmode] .PanelUI-subView .subviewbutton, diff --git a/intl/strres/nsStringBundle.cpp b/intl/strres/nsStringBundle.cpp index 0e8a277b1fea..0e9d059fbf20 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 731b216f403e35454b2b44da4a7d89772e918c0f 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 | 4 +- 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 | 8 ++++ mobile/android/geckoview/build.gradle | 1 + mobile/android/moz.configure | 21 ++++++++- mobile/android/torbrowser.configure | 30 ++++++++++++ moz.configure | 81 +++++++++++++++++++++++++++++++++ mozconfig-android-all-dev | 16 +++++++ mozconfig-android-armv7 | 37 +++++++++++++++ mozconfig-linux-arm | 2 +- 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 ++ 25 files changed, 253 insertions(+), 19 deletions(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index 6e093f919c29..80fe3f56967b 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -232,7 +232,7 @@ pref("privacy.firstparty.isolate", true); // Always enforce first party isolatio 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.http.http2.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); @@ -387,6 +387,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 2d598b9a6e6b..b53346a5ad53 100644 --- a/browser/base/moz.build +++ b/browser/base/moz.build @@ -87,6 +87,9 @@ if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["MOZ_DEFAULT_BROWSER_AGENT"]: # Impacts `/toolkit/content/license.html`. DEFINES["MOZ_DEFAULT_BROWSER_AGENT"] = True
+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 3281543dc71a..1896b995a8d4 100644 --- a/browser/config/mozconfigs/base-browser +++ b/browser/config/mozconfigs/base-browser @@ -32,5 +32,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 000000000000..8969a88aeaf9 --- /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 2e10e16f59a6..f5411547c0a7 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 8653bcbb165d..5a0b722b915e 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 c1041f317d0f..59d8199b4947 100644 --- a/mobile/android/confvars.sh +++ b/mobile/android/confvars.sh @@ -15,3 +15,11 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=mobile/android/branding/official 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 9cb599d89b6d..194198efdd41 100644 --- a/mobile/android/geckoview/build.gradle +++ b/mobile/android/geckoview/build.gradle @@ -46,6 +46,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 66c3a7c8eb56..eedaa8c2b1d1 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( @@ -58,7 +58,10 @@ option( set_config("MOZ_ANDROID_GECKOVIEW_LITE", True, when="--enable-geckoview-lite")
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)
@@ -77,6 +80,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") @@ -94,3 +99,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 000000000000..bcb725cae121 --- /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 11d93c563a5d..411a2ebbfbeb 100755 --- a/moz.configure +++ b/moz.configure @@ -1036,6 +1036,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-all-dev b/mozconfig-android-all-dev new file mode 100644 index 000000000000..1d3d612eca97 --- /dev/null +++ b/mozconfig-android-all-dev @@ -0,0 +1,16 @@ +export MOZILLA_OFFICIAL=1 + +ac_add_options --enable-application=mobile/android +ac_add_options --disable-compile-environment + +# https://bugzilla.mozilla.org/show_bug.cgi?id=1758568 +ac_add_options --enable-minify=properties + +# You must use the "default" bogus channel for dev builds +ac_add_options --enable-update-channel=default + +ac_add_options --without-wasm-sandboxed-libraries +ac_add_options --with-tor-browser-version=dev-build + +ac_add_options --with-java-bin-path=/usr/lib/jvm/java-11-openjdk-amd64/bin +ac_add_options --with-android-sdk=$HOME/Android/Sdk diff --git a/mozconfig-android-armv7 b/mozconfig-android-armv7 new file mode 100644 index 000000000000..6ea51b0d5c05 --- /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-arm b/mozconfig-linux-arm index 0a476ed03bb4..33546709108a 100644 --- a/mozconfig-linux-arm +++ b/mozconfig-linux-arm @@ -1,4 +1,4 @@ -. $topsrcdir/browser/config/mozconfigs/base-browser +. $topsrcdir/browser/config/mozconfigs/tor-browser
ac_add_options --target=arm-linux-gnueabihf
diff --git a/mozconfig-linux-i686 b/mozconfig-linux-i686 index 16a75279e665..4b03732c0e06 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 f14f1f4df86b..06cf6e75b14f 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 62ca3feaad99..ec9c8c086eca 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 1e68b1c85103..14488d870ddb 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 0fbda17fa63c..e2286f972e62 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 1b9997d01e8e..78ff05ecc810 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 e65ccce89db7..93b7a28fd361 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 5fc36caa1903..d830a88878cd 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 8a8b06f4b508..90571bb3e256 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 e19078ee31c9..29968b3b612e 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@", @@ -467,6 +475,13 @@ this.AppConstants = Object.freeze({ false, #endif
+ TOR_BROWSER_UPDATE: +#ifdef TOR_BROWSER_UPDATE + true, +#else + false, +#endif + // Returns true for CN region build when distibution id set as 'MozillaOnline' isChinaRepack() { return ( diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index f8f65aef789e..a537997c78ee 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -302,6 +302,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit db2e93d421bff5d961b0fa8132ee4e0d5ca06bbb 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 | 96 +++++++++++++++++++++++++++ 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, 146 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 000000000000..9a269f5a9632 --- /dev/null +++ b/browser/app/profile/000-tor-browser.js @@ -0,0 +1,96 @@ +#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","new-identity-button","downloads-button"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"toolbar-menubar":["menubar-items"],"PanelUI-contents":["home-button","edit-controls", [...] + +// Treat .onions as secure +pref("dom.securecontext.allowlist_onions", true); + +// Bug 40423/41137: Disable http/3 +// We should re-enable it as soon as Tor gets UDP support +pref("network.http.http3.enabled", false); + +#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_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 ee46e9b7d30d..053ae3737bf6 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 0df0a824f9ad..d72932988fac 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 000000000000..61c8a0cd7fa1 --- /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 d354c81e71d3..ddff137ae9b4 100644 --- a/taskcluster/ci/source-test/mozlint.yml +++ b/taskcluster/ci/source-test/mozlint.yml @@ -151,7 +151,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 6cd742c3080669e9d2a5ab96a5f15ee31d22f3c1 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 b98668607c9b..7b5752b4c20f 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2996,6 +2996,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; @@ -4017,6 +4032,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 + mozilla::startup::IncreaseDescriptorLimits();
#ifdef USE_GLX_TEST @@ -4938,7 +4961,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 " @@ -5046,12 +5096,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 0454863ac07a..c39c1fabd57c 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 @@ -1332,7 +1334,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; @@ -1342,6 +1350,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); @@ -1489,6 +1498,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 a9d017d18e0f..f7270905aded 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 000000000000..30a78f18123e --- /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 000000000000..22cae5a660f4 --- /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 a6bcba45515a..152f691ae02b 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 += [ @@ -136,6 +137,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 60b1775eda97..85ce71c5016c 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 2810ae48b167235b668e730c00650299a57b6818 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/components/sessionstore/SessionStore.jsm | 5 + browser/modules/BridgeDB.jsm | 61 ++ browser/modules/Moat.jsm | 814 +++++++++++++++ browser/modules/TorConnect.jsm | 1081 ++++++++++++++++++++ browser/modules/TorProtocolService.jsm | 510 +++++++++ browser/modules/TorSettings.jsm | 788 ++++++++++++++ browser/modules/moz.build | 4 + .../processsingleton/MainProcessSingleton.jsm | 3 + 8 files changed, 3266 insertions(+)
diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 4611737ca918..33724bdc1ccb 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -234,6 +234,11 @@ ChromeUtils.defineModuleGetter( "resource://gre/modules/sessionstore/SessionHistory.jsm" );
+// FIXME: Is this really necessary? +const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + XPCOMUtils.defineLazyServiceGetters(this, { gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"], }); diff --git a/browser/modules/BridgeDB.jsm b/browser/modules/BridgeDB.jsm new file mode 100644 index 000000000000..50665710ebf4 --- /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 000000000000..90a6ae4e521c --- /dev/null +++ b/browser/modules/Moat.jsm @@ -0,0 +1,814 @@ +"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 + get status() { + return this._promise; + } + + onStartRequest(request) {} + + // resolve or reject our Promise + onStopRequest(request, status) { + let statuses = {}; + try { + statuses = { + components: status, + successful: Components.isSuccessCode(status), + }; + 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); + } + 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); + let settings = {}; + 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) { + settings.settings = this._fixupSettingsList(response.settings); + } + if ("country" in response) { + settings.country = response.country; + } + return settings; + } + + // 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; + } + + // 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 new file mode 100644 index 000000000000..cc0eeb2b1eba --- /dev/null +++ b/browser/modules/TorConnect.jsm @@ -0,0 +1,1081 @@ +"use strict"; + +var EXPORTED_SYMBOLS = [ + "InternetStatus", + "TorConnect", + "TorConnectTopics", + "TorConnectState", +]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +const { BrowserWindowTracker } = ChromeUtils.import( + "resource:///modules/BrowserWindowTracker.jsm" +); + +const { + TorProtocolService, + TorTopics, + TorBootstrapRequest, +} = ChromeUtils.import("resource:///modules/TorProtocolService.jsm"); + +const { TorLauncherUtil } = ChromeUtils.import( + "resource://torlauncher/modules/tl-util.jsm" +); + +const { + TorSettings, + TorSettingsTopics, + TorBuiltinBridgeTypes, +} = 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", +}); + +/* + 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 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, + _internetStatus: InternetStatus.Unknown, + // 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; + })() + ), + _detectedLocation: "", + _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 */ + _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) => { + // 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; + 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; + } + + 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(); + }; + + tbr.onbootstrapstatus = (progress, status) => { + TorConnect._updateBootstrapStatus(progress, status); + }; + tbr.onbootstrapcomplete = () => { + internetTest.cancel(); + TorConnect._changeState(TorConnectState.Bootstrapped); + }; + tbr.onbootstraperror = (message, details) => { + // 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(); + }); + }), + ], + /* 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: 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; + } + + const settings = await this.mrpc.circumvention_settings( + [...TorBuiltinBridgeTypes, "vanilla"], + countryCode + ); + + if (this.transitioning) { + return; + } + + if (settings?.country) { + TorConnect._detectedLocation = settings.country; + } + if (settings?.settings && settings.settings.length) { + this.settings = settings.settings; + } else { + try { + this.settings = await this.mrpc.circumvention_defaults([ + ...TorBuiltinBridgeTypes, + "vanilla", + ]); + } catch (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 (!TorConnect._detectedLocation) { + // 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 + 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 (errRestore) { + console.log( + `TorConnect: Failed to restore original settings => ${errRestore}` + ); + } + // 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; + + Services.obs.notifyObservers( + { message: errorMessage, details: errorDetails }, + 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(state) { + return this._stateCallbacks.get(state); + }, + + _changeState(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(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() { + console.log("TorConnect: init()"); + + // delay remaining init until after profile-after-change + Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange); + + this._callback(TorConnectState.Initial).begin(); + }, + + async observe(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 = addTopic => { + Services.obs.addObserver(this, addTopic); + console.log(`TorConnect: Observing topic '${addTopic}'`); + }; + + // 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 internetStatus() { + return this._internetStatus; + }, + + get countryCodes() { + return this._countryCodes; + }, + + get countryNames() { + return this._countryNames; + }, + + get detectedLocation() { + return this._detectedLocation; + }, + + get errorMessage() { + return this._errorMessage; + }, + + get errorDetails() { + return this._errorDetails; + }, + + get logHasWarningOrError() { + return this._logHasWarningOrError; + }, + + get hasBootstrapEverFailed() { + return this._hasBootstrapEverFailed; + }, + + get uiState() { + return this._uiState; + }, + set uiState(newState) { + this._uiState = newState; + }, + + /* + These functions allow external consumers to tell TorConnect to transition states + */ + + beginBootstrap() { + console.log("TorConnect: beginBootstrap()"); + this._changeState(TorConnectState.Bootstrapping); + }, + + cancelBootstrap() { + console.log("TorConnect: cancelBootstrap()"); + this._changeState(TorConnectState.Configuring); + }, + + beginAutoBootstrap(countryCode) { + console.log("TorConnect: beginAutoBootstrap()"); + this._changeState(TorConnectState.AutoBootstrapping, countryCode); + }, + + cancelAutoBootstrap() { + console.log("TorConnect: cancelAutoBootstrap()"); + this._changeState(TorConnectState.Configuring); + }, + + /* + Further external commands and helper methods + */ + openTorPreferences() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:preferences#connection", true); + }, + + openTorConnect() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:torconnect", true, { + ignoreQueryString: true, + }); + }, + + viewTorLogs() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:preferences#connection-viewlogs", true); + }, + + async getCountryCodes() { + // 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(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(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 000000000000..6a1d6b94fff7 --- /dev/null +++ b/browser/modules/TorProtocolService.jsm @@ -0,0 +1,510 @@ +// 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 + bootstrap() { + if (this._bootstrapPromise) { + return this._bootstrapPromise; + } + + this._bootstrapPromise = new Promise((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 + TorProtocolService.connect() + .then(async err => { + if (!err) { + return; + } + + clearTimeout(this._timeoutID); + await TorProtocolService.torStopBootstrap(); + + const message = err.message; + const details = err.details; + if (this.onbootstraperror) { + this.onbootstraperror(message, details); + } + this._bootstrapPromiseResolve(false); + }) + .catch(err => { + // Currently, TorProtocolService.connect() should never throw + reject(err); + }); + }).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 000000000000..57a2a80c7d3f --- /dev/null +++ b/browser/modules/TorSettings.jsm @@ -0,0 +1,788 @@ +"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( + (() => { + const bridgeListBranch = Services.prefs.getBranch( + TorLauncherPrefs.default_bridge + ); + const bridgePrefs = bridgeListBranch.getChildList(""); + + // an unordered set for shoving bridge types into + const 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 + const recommendedBridgeType = Services.prefs.getCharPref( + TorLauncherPrefs.default_bridge_recommended_type, + null + ); + + const 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 +const 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 + const port = Number.parseInt(aPort); + if (port < 1 || port > 65535) { + return 0; + } + + return port; +}; + +// 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 +const 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 + const 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 +const parsePortList = function(aPortListString) { + const splitStrings = aPortListString.split(","); + // parse and remove duplicates + const 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); +}; + +const getBuiltinBridgeStrings = function(builtinType) { + if (!builtinType) { + return []; + } + + const bridgeBranch = Services.prefs.getBranch( + TorLauncherPrefs.default_bridge + ); + const bridgeBranchPrefs = bridgeBranch.getChildList(""); + const retval = []; + + // regex matches against strings ending in ".N" where N is a positive integer + const 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) + ) { + const 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 */ + +const 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; + } +}; + +const arrayCopy = function(array) { + return [].concat(array); +}; + +/* TorSettings module */ + +const TorSettings = (() => { + const self = { + _settings: null, + + // tor daemon related settings + defaultSettings() { + const 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() { + 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 */ + async observe(subject, topic, data) { + console.log(`TorSettings: Observed ${topic}`); + + // once the tor daemon is ready, we need to apply our settings + const 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() { + console.log("TorSettings: loadFromPrefs()"); + + const 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) { + const 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) { + // 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 = []; + const 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) { + const 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() { + console.log("TorSettings: saveToPrefs()"); + + const 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 + const 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 + async applySettings() { + console.log("TorSettings: applySettings()"); + const settings = this._settings; + const settingsMap = new Map(); + + /* Bridges */ + const haveBridges = + settings.bridges.enabled && !!settings.bridges.bridge_strings.length; + 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) { + const address = settings.proxy.address; + const port = settings.proxy.port; + const username = settings.proxy.username; + const 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) { + const 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(settings) { + console.log("TorSettings: setSettings()"); + const 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 && + 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() { + 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) { + 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 dc73d9fbccdd..b29d496879d6 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -121,6 +121,7 @@ EXTRA_JS_MODULES += [ "AboutNewTab.jsm", "AppUpdater.jsm", "AsyncTabSwitcher.jsm", + "BridgeDB.jsm", "BrowserUIUtils.jsm", "BrowserUsageTelemetry.jsm", "BrowserWindowTracker.jsm", @@ -131,6 +132,7 @@ EXTRA_JS_MODULES += [ "FaviconLoader.jsm", "HomePage.jsm", "LaterRun.jsm", + 'Moat.jsm', "NewTabPagePreloading.jsm", "OpenInTabsUtils.jsm", "PageActions.jsm", @@ -144,6 +146,8 @@ EXTRA_JS_MODULES += [ "SitePermissions.jsm", "TabsList.jsm", "TabUnloader.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 4f800b93fbce..28a16b732172 100644 --- a/toolkit/components/processsingleton/MainProcessSingleton.jsm +++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm @@ -20,6 +20,9 @@ MainProcessSingleton.prototype = { // Imported for side-effects. ChromeUtils.import("resource://gre/modules/CustomElementsListener.jsm");
+ // FIXME: Is this import really necessary? + ChromeUtils.import("resource:///modules/TorSettings.jsm"); + Services.ppmm.loadProcessScript( "chrome://global/content/process-content.js", true
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 99228a42258586ab42edd92779b6046474825e63 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 | 40 ++++++++++++++------ browser/base/content/appmenu-viewcache.inc.xhtml | 11 +++++- browser/base/content/browser-doctype.inc | 8 ++++ browser/base/content/browser-menubar.inc | 44 +++++++++++++++------- browser/base/content/browser-sets.inc | 1 + browser/base/content/browser.js | 1 + browser/base/content/browser.xhtml | 13 +++++++ browser/base/content/navigator-toolbox.inc.xhtml | 5 +++ 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 +- browser/themes/shared/icons/new_circuit.svg | 6 +++ browser/themes/shared/jar.inc.mn | 2 + browser/themes/shared/toolbarbutton-icons.css | 4 ++ docshell/base/nsAboutRedirector.cpp | 6 ++- docshell/build/components.conf | 1 + mobile/android/installer/package-manifest.in | 4 ++ toolkit/moz.build | 1 + toolkit/torproject/torbutton | 1 + .../lib/environments/browser-window.js | 6 ++- 25 files changed, 162 insertions(+), 78 deletions(-)
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..2f03bd8e22df --- /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 90a568a17dd6..60b1e15b637c 100644 --- a/browser/base/content/aboutDialog.xhtml +++ b/browser/base/content/aboutDialog.xhtml @@ -7,6 +7,12 @@ <?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 [ +#include browser-doctype.inc +]>
<window xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" @@ -22,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 @@ -140,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 a67f89bd5f70..82c319d310eb 100644 --- a/browser/base/content/appmenu-viewcache.inc.xhtml +++ b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -55,7 +55,8 @@ class="subviewbutton" data-l10n-id="appmenuitem-new-private-window" key="key_privatebrowsing" - command="Tools:PrivateBrowsing"/> + command="Tools:PrivateBrowsing" + hidden="true"/> <toolbarseparator/> <toolbarbutton id="appMenu-bookmarks-button" class="subviewbutton subviewbutton-nav" @@ -175,11 +176,17 @@ <toolbarbutton id="appMenu-restoreSession" data-l10n-id="appmenu-restore-session" class="subviewbutton" - command="Browser:RestoreLastSession"/> + command="Browser:RestoreLastSession" + hidden="true"/> <toolbarseparator/> <toolbarbutton id="appMenu-new-identity" class="subviewbutton" key="new-identity-key"/> + <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="appMenuClearRecentHistory" data-l10n-id="appmenu-clear-history" diff --git a/browser/base/content/browser-doctype.inc b/browser/base/content/browser-doctype.inc new file mode 100644 index 000000000000..fdc302e79de3 --- /dev/null +++ b/browser/base/content/browser-doctype.inc @@ -0,0 +1,8 @@ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +<!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 973f3c5c98f6..a86d5fe8b900 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -31,6 +31,11 @@ <menuseparator/> <menuitem id="menu_newIdentity" key="new-identity-key"/> + <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" @@ -455,44 +460,55 @@ <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('about:manual', {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-share-ideas" - appmenu-data-l10n-id="appmenu-help-share-ideas"/> - <menuitem id="helpSafeMode" + data-l10n-id="menu-help-feedback-page" + 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"/> - <menuitem id="troubleShooting" + appmenu-data-l10n-id="appmenu-help-enter-troubleshoot-mode2"/> --> + <!-- <menuitem id="troubleShooting" oncommand="openTroubleshootingPage()" data-l10n-id="menu-help-more-troubleshooting-info" - appmenu-data-l10n-id="appmenu-help-more-troubleshooting-info"/> + appmenu-data-l10n-id="appmenu-help-more-troubleshooting-info"/> --> <menuitem id="help_reportSiteIssue" oncommand="ReportSiteIssue();" 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 17edb35baf6a..dc5d93978950 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -389,4 +389,5 @@ internal="true"/> #endif <key id="new-identity-key" modifiers="accel shift" key="U" oncommand="NewIdentityButton.onCommand(event)"/> + <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 d14f5c1439dc..5b2b12d79922 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -645,6 +645,7 @@ var gPageIcons = { };
var gInitialPages = [ + "about:tor", "about:blank", "about:home", ...(AppConstants.NIGHTLY_BUILD ? ["about:firefoxview"] : []), diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 5f53fa119435..cb29fa7bd280 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -35,6 +35,12 @@ <?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"?> + +<!DOCTYPE window [ +#include browser-doctype.inc +]>
<html id="main-window" xmlns:html="http://www.w3.org/1999/xhtml" @@ -114,11 +120,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/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index cadf68c91679..6123e1336aed 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -539,6 +539,11 @@
<toolbarbutton id="new-identity-button" class="toolbarbutton-1 chromeclass-toolbar-additional"/>
+ <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/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 99b289bd934a..6d0947a464ba 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -18,7 +18,6 @@ const { AppConstants } = ChromeUtils.import( );
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", @@ -225,28 +224,6 @@ let JSWINDOWACTORS = { remoteTypes: ["privilegedabout"], },
- 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", @@ -1599,8 +1576,6 @@ BrowserGlue.prototype = {
// the first browser window has finished initializing _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) { - AboutNewTab.init(); - TabCrashHandler.init();
ProcessHangMonitor.init(); @@ -5792,12 +5767,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 ad6f9db340ef..498f374ffde8 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 f3bc40019f8f..471a3139baa7 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 5bb936782ed9..e812d969837e 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 8706870466fa..f1a8115843a3 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -15,7 +15,10 @@ <?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?> <?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.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 053ae3737bf6..6c8c3eaf7caa 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -234,6 +234,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 #ifdef MOZ_GTK diff --git a/browser/modules/HomePage.jsm b/browser/modules/HomePage.jsm index f73b0f3e6c8c..26618374df3a 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/browser/themes/shared/icons/new_circuit.svg b/browser/themes/shared/icons/new_circuit.svg new file mode 100644 index 000000000000..ddc819946818 --- /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/jar.inc.mn b/browser/themes/shared/jar.inc.mn index d04f95e59ea0..95fe3fdbe299 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -266,3 +266,5 @@ skin/classic/browser/syncedtabs/sidebar.css (../shared/syncedtabs/sidebar.css)
skin/classic/browser/new_identity.svg (../shared/icons/new_identity.svg) + + skin/classic/browser/new_circuit.svg (../shared/icons/new_circuit.svg) diff --git a/browser/themes/shared/toolbarbutton-icons.css b/browser/themes/shared/toolbarbutton-icons.css index 8e285fdfd7c2..b2cb4d277e1e 100644 --- a/browser/themes/shared/toolbarbutton-icons.css +++ b/browser/themes/shared/toolbarbutton-icons.css @@ -267,6 +267,10 @@ toolbar { 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"); } diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp index e28bec9bd2c2..232104214844 100644 --- a/docshell/base/nsAboutRedirector.cpp +++ b/docshell/base/nsAboutRedirector.cpp @@ -174,7 +174,11 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, - {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}}; + {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"tor", "chrome://torbutton/content/aboutTor/aboutTor.xhtml", + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}}; static const int kRedirTotal = mozilla::ArrayLength(kRedirMap);
NS_IMETHODIMP diff --git a/docshell/build/components.conf b/docshell/build/components.conf index 5f11df641e37..6bc8617c8f0a 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 3149f3ac9c6b..29ffe95fa27c 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -134,6 +134,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 d464f7eb8092..a791014ce2d5 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/torproject/torbutton b/toolkit/torproject/torbutton new file mode 160000 index 000000000000..d2bfae400363 --- /dev/null +++ b/toolkit/torproject/torbutton @@ -0,0 +1 @@ +Subproject commit d2bfae400363680d333affdf2d1b8ffe90c2fa16 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 c24943e9dee3..de0091ae8d4d 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 @@ -83,7 +83,11 @@ function getGlobalScriptIncludes(scriptPath) { "browser/components/screenshots/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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 149a3ffb1fce6fbaf1e479bfa6d2463f1d7ce88f 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 +- 3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build index 39bbc2937271..df3a43a07fd7 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -5,3 +5,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += ["onboarding"] + +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 6c8c3eaf7caa..84939f55d210 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -234,6 +234,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 14488d870ddb..ce66cd7cdc3e 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
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 892a822085a69fde2b40fb8f4b397c7477e08bbd 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 2fb4015f4126..5925da91d6da 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 @@ -28,6 +28,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; @@ -38,7 +42,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit eb0d02ede5d59540f8da950a068c996bc93cf9e3 Author: Alex Catarineu acat@torproject.org AuthorDate: Fri Jul 24 21:15:20 2020 +0200
Add TorStrings module for localization --- browser/modules/TorStrings.jsm | 942 +++++++++++++++++++++++++++++++++++++++++ browser/modules/moz.build | 1 + 2 files changed, 943 insertions(+)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm new file mode 100644 index 000000000000..5c28b4a2ad97 --- /dev/null +++ b/browser/modules/TorStrings.jsm @@ -0,0 +1,942 @@ +"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})`; + } +} + +var TorStrings = { + /* + 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" + ), + 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 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" + ), + 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"), + // 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 + 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: "about:manual#about", + learnMoreBridgesURL: "about:manual#bridges", + learnMoreBridgesCardURL: "about:manual#bridges_bridge-moji", + learnMoreCircumventionURL: "about:manual#circumvention", + }; + + 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…" + ), + + 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." + ), + + 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.tryingBridgeAgain", + "Trying one more time…" + ), + + errorLocation: getStringNet( + "torConnect.errorLocation", + "Tor Browser couldn’t locate you" + ), + + errorLocationDescription: getStringNet( + "torConnect.errorLocationDescription", + "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( + "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 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" + ), + 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"), + + automatic: getStringNet("torConnect.automatic", "Automatic"), + selectCountryRegion: getStringNet( + "torConnect.selectCountryRegion", + "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" + ), + 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: "about:manual#onion-services", + // XUL popups cannot open about: URLs, but we are online when showing the notification, so just use the online version + learnMoreURLNotification: `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 */, + + /* + 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 + */ + + // 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 b29d496879d6..018ca1a9430b 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -148,6 +148,7 @@ EXTRA_JS_MODULES += [ "TabUnloader.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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 8020ef3b23c120d88e3c6629c102a671da988126 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 dbd3041cfb6e..afe54b5a481f 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 f1ea2aa51939..74a984a5e4e6 100644 --- a/toolkit/profile/nsToolkitProfileService.cpp +++ b/toolkit/profile/nsToolkitProfileService.cpp @@ -1251,9 +1251,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. @@ -1286,7 +1287,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; } @@ -1379,6 +1381,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); @@ -2145,3 +2154,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 d281d39ebe59..5c97c906df49 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 7b5752b4c20f..686d8dd985e6 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2683,6 +2683,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";
@@ -2756,7 +2841,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 @@ -2781,11 +2866,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) { @@ -2815,24 +2901,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 @@ -3018,6 +3119,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 @@ -3044,7 +3152,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 @@ -3059,7 +3168,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; @@ -3105,9 +3215,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); @@ -5004,7 +5119,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit ae81c697dfb66792ec5454a19e728f91abfee24d Author: sanketh me@snkth.com AuthorDate: Mon Feb 8 20:12:44 2021 -0500
Bug 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 | 36 +++++++ browser/themes/shared/browser-shared.css | 5 + toolkit/content/license.html | 32 ++++++ toolkit/modules/Bech32Decode.jsm | 103 +++++++++++++++++++ toolkit/modules/moz.build | 1 + 10 files changed, 440 insertions(+)
diff --git a/browser/actors/CryptoSafetyChild.jsm b/browser/actors/CryptoSafetyChild.jsm new file mode 100644 index 000000000000..87ff261d4915 --- /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 000000000000..bac151df5511 --- /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 87ba6ef0c2a8..5a2c1796fb5d 100644 --- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -57,6 +57,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 69d53bdd7662..443441395a49 100644 --- a/browser/base/content/popup-notifications.inc +++ b/browser/base/content/popup-notifications.inc @@ -166,3 +166,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 6d0947a464ba..b1a0fc5ffe44 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -453,6 +453,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 5c28b4a2ad97..cd15386c8606 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -96,6 +96,42 @@ class TorPropertyStringBundle { }
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 about:preferences#connection Strings */ diff --git a/browser/themes/shared/browser-shared.css b/browser/themes/shared/browser-shared.css index 946807ef8623..1a1bb759b8b4 100644 --- a/browser/themes/shared/browser-shared.css +++ b/browser/themes/shared/browser-shared.css @@ -829,3 +829,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 b213750f69d3..a1d9e137f150 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -71,6 +71,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> @@ -2106,6 +2107,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 000000000000..c793a6282291 --- /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, data: data.slice(0, data.length - 6) }; +} diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index a537997c78ee..47b386bcd507 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -155,6 +155,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 2cce8efa3cede0324b58e2e9a6b2b40e5a48bb87 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 | 192 +++++++++++++++++---- uriloader/exthandler/nsExternalHelperAppService.h | 3 + .../exthandler/nsIExternalHelperAppService.idl | 47 +++++ 3 files changed, 209 insertions(+), 33 deletions(-)
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index c86b006a6b4c..933f7f1710bf 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -144,6 +144,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"; + StaticRefPtr<nsIFile> sFallbackDownloadDir;
// Helper functions for Content-Disposition headers @@ -395,6 +398,22 @@ nsresult GenerateRandomName(nsACString& result) { 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 @@ -592,6 +611,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;
/** @@ -614,6 +723,9 @@ nsExternalHelperAppService::GetSingleton() { return do_AddRef(sExtHelperAppSvcSingleton); }
+////////////////////////////////////////////////////////////////////////////////////////////////////// +// nsExternalHelperAppService definition and implementation +////////////////////////////////////////////////////////////////////////////////////////////////////// NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService, nsPIExternalAppLauncher, nsIExternalProtocolService, nsIMIMEService, nsIObserver, nsISupportsWeakReference) @@ -1126,14 +1238,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; }
////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1278,6 +1391,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) @@ -1533,18 +1647,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) { @@ -1692,6 +1795,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...
@@ -1837,7 +1968,7 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { GetTargetFile(getter_AddRefs(fileToTest)); if (fileToTest) { bool isExecutable; - rv = fileToTest->IsExecutable(&isExecutable); + nsresult rv = fileToTest->IsExecutable(&isExecutable); if (NS_FAILED(rv) || mTempFileIsExecutable || isExecutable) { // checking NS_FAILED, because paranoia is good alwaysAsk = true; @@ -1853,20 +1984,7 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { } #endif
- nsAutoCString actionTelem; - if (alwaysAsk) { - actionTelem.AssignLiteral("ask"); - } else if (shouldAutomaticallyHandleInternally) { - actionTelem.AssignLiteral("internal"); - } else if (action == nsIMIMEInfo::useHelperApp || - action == nsIMIMEInfo::useSystemDefault) { - actionTelem.AssignLiteral("external"); - } else { - actionTelem.AssignLiteral("save"); - } - - RecordDownloadTelemetry(aChannel, actionTelem.get()); - + nsresult rv = NS_OK; if (alwaysAsk) { // Display the dialog mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv); @@ -1899,6 +2017,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); +} + void nsExternalAppHandler::RecordDownloadTelemetry(nsIChannel* aChannel, const char* aAction) { // Telemetry for helper app dialog @@ -2762,7 +2888,7 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) { }
// Break our reference cycle with the helper app dialog (set up in - // OnStartRequest) + // ContinueRequest) mDialog = nullptr; mDialogShowing = false;
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h index 439be1645f4f..af5668d4034b 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.h +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -256,6 +256,7 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService, */ class nsExternalAppHandler final : public nsIStreamListener, public nsIHelperAppLauncher, + public nsIHelperAppWarningLauncher, public nsIBackgroundFileSaverObserver, public nsINamed { public: @@ -263,6 +264,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 @@ -539,6 +541,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 307e6196a89d..632c6d585ffb 100644 --- a/uriloader/exthandler/nsIExternalHelperAppService.idl +++ b/uriloader/exthandler/nsIExternalHelperAppService.idl @@ -189,3 +189,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 47154f90c98c92e2c9376363881f2e9af4583742 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 a1d9e137f150..4973c4b2747f 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -156,6 +156,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> @@ -5128,6 +5129,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 000000000000..a5970ed2b7b3 --- /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 47b386bcd507..c2baf6c8bbbc 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -204,6 +204,7 @@ EXTRA_JS_MODULES += [ "ProcessType.jsm", "ProfileAge.jsm", "PromiseUtils.jsm", + "QRCode.jsm", "Region.jsm", "RemotePageAccessManager.jsm", "ResetProfile.jsm",
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit dffd7bd7b066fdf1379f5c6caf194365fc0b2bd7 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 | 5 + .../torpreferences/content/bridgeQrDialog.jsm | 51 + .../torpreferences/content/bridgeQrDialog.xhtml | 25 + .../content/bridgemoji-annotations.json | 9032 ++++++++++++++++++++ .../torpreferences/content/bridgemoji/1f300.svg | 1 + .../torpreferences/content/bridgemoji/1f308.svg | 1 + .../torpreferences/content/bridgemoji/1f30a.svg | 1 + .../torpreferences/content/bridgemoji/1f30b.svg | 1 + .../torpreferences/content/bridgemoji/1f319.svg | 1 + .../torpreferences/content/bridgemoji/1f31f.svg | 1 + .../torpreferences/content/bridgemoji/1f321.svg | 1 + .../torpreferences/content/bridgemoji/1f32d.svg | 1 + .../torpreferences/content/bridgemoji/1f32e.svg | 1 + .../torpreferences/content/bridgemoji/1f332.svg | 1 + .../torpreferences/content/bridgemoji/1f333.svg | 1 + .../torpreferences/content/bridgemoji/1f334.svg | 1 + .../torpreferences/content/bridgemoji/1f335.svg | 1 + .../torpreferences/content/bridgemoji/1f336.svg | 1 + .../torpreferences/content/bridgemoji/1f337.svg | 1 + .../torpreferences/content/bridgemoji/1f339.svg | 1 + .../torpreferences/content/bridgemoji/1f33a.svg | 1 + .../torpreferences/content/bridgemoji/1f33b.svg | 1 + .../torpreferences/content/bridgemoji/1f33d.svg | 1 + .../torpreferences/content/bridgemoji/1f33f.svg | 1 + .../torpreferences/content/bridgemoji/1f341.svg | 1 + .../torpreferences/content/bridgemoji/1f344.svg | 1 + .../torpreferences/content/bridgemoji/1f345.svg | 1 + .../torpreferences/content/bridgemoji/1f346.svg | 1 + .../torpreferences/content/bridgemoji/1f347.svg | 1 + .../torpreferences/content/bridgemoji/1f348.svg | 1 + .../torpreferences/content/bridgemoji/1f349.svg | 1 + .../torpreferences/content/bridgemoji/1f34a.svg | 1 + .../torpreferences/content/bridgemoji/1f34b.svg | 1 + .../torpreferences/content/bridgemoji/1f34c.svg | 1 + .../torpreferences/content/bridgemoji/1f34d.svg | 1 + .../torpreferences/content/bridgemoji/1f34f.svg | 1 + .../torpreferences/content/bridgemoji/1f350.svg | 1 + .../torpreferences/content/bridgemoji/1f351.svg | 1 + .../torpreferences/content/bridgemoji/1f352.svg | 1 + .../torpreferences/content/bridgemoji/1f353.svg | 1 + .../torpreferences/content/bridgemoji/1f354.svg | 1 + .../torpreferences/content/bridgemoji/1f355.svg | 1 + .../torpreferences/content/bridgemoji/1f368.svg | 1 + .../torpreferences/content/bridgemoji/1f369.svg | 1 + .../torpreferences/content/bridgemoji/1f36a.svg | 1 + .../torpreferences/content/bridgemoji/1f36b.svg | 1 + .../torpreferences/content/bridgemoji/1f36c.svg | 1 + .../torpreferences/content/bridgemoji/1f36d.svg | 1 + .../torpreferences/content/bridgemoji/1f37f.svg | 1 + .../torpreferences/content/bridgemoji/1f380.svg | 1 + .../torpreferences/content/bridgemoji/1f381.svg | 1 + .../torpreferences/content/bridgemoji/1f382.svg | 1 + .../torpreferences/content/bridgemoji/1f383.svg | 1 + .../torpreferences/content/bridgemoji/1f388.svg | 1 + .../torpreferences/content/bridgemoji/1f389.svg | 1 + .../torpreferences/content/bridgemoji/1f38f.svg | 1 + .../torpreferences/content/bridgemoji/1f392.svg | 1 + .../torpreferences/content/bridgemoji/1f399.svg | 1 + .../torpreferences/content/bridgemoji/1f39f.svg | 1 + .../torpreferences/content/bridgemoji/1f3a0.svg | 1 + .../torpreferences/content/bridgemoji/1f3a1.svg | 1 + .../torpreferences/content/bridgemoji/1f3a2.svg | 1 + .../torpreferences/content/bridgemoji/1f3a8.svg | 1 + .../torpreferences/content/bridgemoji/1f3ac.svg | 1 + .../torpreferences/content/bridgemoji/1f3af.svg | 1 + .../torpreferences/content/bridgemoji/1f3b2.svg | 1 + .../torpreferences/content/bridgemoji/1f3b6.svg | 1 + .../torpreferences/content/bridgemoji/1f3b7.svg | 1 + .../torpreferences/content/bridgemoji/1f3b8.svg | 1 + .../torpreferences/content/bridgemoji/1f3ba.svg | 1 + .../torpreferences/content/bridgemoji/1f3bb.svg | 1 + .../torpreferences/content/bridgemoji/1f3be.svg | 1 + .../torpreferences/content/bridgemoji/1f3c0.svg | 1 + .../torpreferences/content/bridgemoji/1f3c6.svg | 1 + .../torpreferences/content/bridgemoji/1f3c8.svg | 1 + .../torpreferences/content/bridgemoji/1f3d3.svg | 1 + .../torpreferences/content/bridgemoji/1f3d4.svg | 1 + .../torpreferences/content/bridgemoji/1f3d5.svg | 1 + .../torpreferences/content/bridgemoji/1f3dd.svg | 1 + .../torpreferences/content/bridgemoji/1f3e1.svg | 1 + .../torpreferences/content/bridgemoji/1f3ee.svg | 1 + .../torpreferences/content/bridgemoji/1f3f7.svg | 1 + .../torpreferences/content/bridgemoji/1f3f8.svg | 1 + .../torpreferences/content/bridgemoji/1f3f9.svg | 1 + .../torpreferences/content/bridgemoji/1f40a.svg | 1 + .../torpreferences/content/bridgemoji/1f40c.svg | 1 + .../torpreferences/content/bridgemoji/1f40d.svg | 1 + .../torpreferences/content/bridgemoji/1f417.svg | 1 + .../torpreferences/content/bridgemoji/1f418.svg | 1 + .../torpreferences/content/bridgemoji/1f419.svg | 1 + .../torpreferences/content/bridgemoji/1f41a.svg | 1 + .../torpreferences/content/bridgemoji/1f41b.svg | 1 + .../torpreferences/content/bridgemoji/1f41d.svg | 1 + .../torpreferences/content/bridgemoji/1f41e.svg | 1 + .../torpreferences/content/bridgemoji/1f41f.svg | 1 + .../torpreferences/content/bridgemoji/1f420.svg | 1 + .../torpreferences/content/bridgemoji/1f422.svg | 1 + .../torpreferences/content/bridgemoji/1f425.svg | 1 + .../torpreferences/content/bridgemoji/1f426.svg | 1 + .../torpreferences/content/bridgemoji/1f428.svg | 1 + .../torpreferences/content/bridgemoji/1f42a.svg | 1 + .../torpreferences/content/bridgemoji/1f42c.svg | 1 + .../torpreferences/content/bridgemoji/1f42d.svg | 1 + .../torpreferences/content/bridgemoji/1f42e.svg | 1 + .../torpreferences/content/bridgemoji/1f42f.svg | 1 + .../torpreferences/content/bridgemoji/1f430.svg | 1 + .../torpreferences/content/bridgemoji/1f431.svg | 1 + .../torpreferences/content/bridgemoji/1f432.svg | 1 + .../torpreferences/content/bridgemoji/1f433.svg | 1 + .../torpreferences/content/bridgemoji/1f434.svg | 1 + .../torpreferences/content/bridgemoji/1f435.svg | 1 + .../torpreferences/content/bridgemoji/1f436.svg | 1 + .../torpreferences/content/bridgemoji/1f437.svg | 1 + .../torpreferences/content/bridgemoji/1f43a.svg | 1 + .../torpreferences/content/bridgemoji/1f43b.svg | 1 + .../torpreferences/content/bridgemoji/1f43f.svg | 1 + .../torpreferences/content/bridgemoji/1f441.svg | 1 + .../torpreferences/content/bridgemoji/1f451.svg | 1 + .../torpreferences/content/bridgemoji/1f455.svg | 1 + .../torpreferences/content/bridgemoji/1f457.svg | 1 + .../torpreferences/content/bridgemoji/1f45f.svg | 1 + .../torpreferences/content/bridgemoji/1f47d.svg | 1 + .../torpreferences/content/bridgemoji/1f484.svg | 1 + .../torpreferences/content/bridgemoji/1f488.svg | 1 + .../torpreferences/content/bridgemoji/1f48d.svg | 1 + .../torpreferences/content/bridgemoji/1f48e.svg | 1 + .../torpreferences/content/bridgemoji/1f490.svg | 1 + .../torpreferences/content/bridgemoji/1f4a1.svg | 1 + .../torpreferences/content/bridgemoji/1f4a7.svg | 1 + .../torpreferences/content/bridgemoji/1f4b3.svg | 1 + .../torpreferences/content/bridgemoji/1f4bf.svg | 1 + .../torpreferences/content/bridgemoji/1f4cc.svg | 1 + .../torpreferences/content/bridgemoji/1f4ce.svg | 1 + .../torpreferences/content/bridgemoji/1f4d5.svg | 1 + .../torpreferences/content/bridgemoji/1f4e1.svg | 1 + .../torpreferences/content/bridgemoji/1f4e2.svg | 1 + .../torpreferences/content/bridgemoji/1f4fb.svg | 1 + .../torpreferences/content/bridgemoji/1f50b.svg | 1 + .../torpreferences/content/bridgemoji/1f511.svg | 1 + .../torpreferences/content/bridgemoji/1f525.svg | 1 + .../torpreferences/content/bridgemoji/1f526.svg | 1 + .../torpreferences/content/bridgemoji/1f52c.svg | 1 + .../torpreferences/content/bridgemoji/1f52d.svg | 1 + .../torpreferences/content/bridgemoji/1f52e.svg | 1 + .../torpreferences/content/bridgemoji/1f54a.svg | 1 + .../torpreferences/content/bridgemoji/1f58c.svg | 1 + .../torpreferences/content/bridgemoji/1f58d.svg | 1 + .../torpreferences/content/bridgemoji/1f5ff.svg | 1 + .../torpreferences/content/bridgemoji/1f680.svg | 1 + .../torpreferences/content/bridgemoji/1f681.svg | 1 + .../torpreferences/content/bridgemoji/1f686.svg | 1 + .../torpreferences/content/bridgemoji/1f68b.svg | 1 + .../torpreferences/content/bridgemoji/1f68d.svg | 1 + .../torpreferences/content/bridgemoji/1f695.svg | 1 + .../torpreferences/content/bridgemoji/1f697.svg | 1 + .../torpreferences/content/bridgemoji/1f69a.svg | 1 + .../torpreferences/content/bridgemoji/1f69c.svg | 1 + .../torpreferences/content/bridgemoji/1f6a0.svg | 1 + .../torpreferences/content/bridgemoji/1f6a2.svg | 1 + .../torpreferences/content/bridgemoji/1f6a4.svg | 1 + .../torpreferences/content/bridgemoji/1f6f0.svg | 1 + .../torpreferences/content/bridgemoji/1f6f4.svg | 1 + .../torpreferences/content/bridgemoji/1f6f5.svg | 1 + .../torpreferences/content/bridgemoji/1f6f6.svg | 1 + .../torpreferences/content/bridgemoji/1f6f8.svg | 1 + .../torpreferences/content/bridgemoji/1f6f9.svg | 1 + .../torpreferences/content/bridgemoji/1f6fa.svg | 1 + .../torpreferences/content/bridgemoji/1f6fc.svg | 1 + .../torpreferences/content/bridgemoji/1f916.svg | 1 + .../torpreferences/content/bridgemoji/1f93f.svg | 1 + .../torpreferences/content/bridgemoji/1f941.svg | 1 + .../torpreferences/content/bridgemoji/1f94c.svg | 1 + .../torpreferences/content/bridgemoji/1f94f.svg | 1 + .../torpreferences/content/bridgemoji/1f950.svg | 1 + .../torpreferences/content/bridgemoji/1f951.svg | 1 + .../torpreferences/content/bridgemoji/1f955.svg | 1 + .../torpreferences/content/bridgemoji/1f956.svg | 1 + .../torpreferences/content/bridgemoji/1f95c.svg | 1 + .../torpreferences/content/bridgemoji/1f95d.svg | 1 + .../torpreferences/content/bridgemoji/1f95e.svg | 1 + .../torpreferences/content/bridgemoji/1f965.svg | 1 + .../torpreferences/content/bridgemoji/1f966.svg | 1 + .../torpreferences/content/bridgemoji/1f968.svg | 1 + .../torpreferences/content/bridgemoji/1f96c.svg | 1 + .../torpreferences/content/bridgemoji/1f96d.svg | 1 + .../torpreferences/content/bridgemoji/1f96f.svg | 1 + .../torpreferences/content/bridgemoji/1f980.svg | 1 + .../torpreferences/content/bridgemoji/1f981.svg | 1 + .../torpreferences/content/bridgemoji/1f984.svg | 1 + .../torpreferences/content/bridgemoji/1f986.svg | 1 + .../torpreferences/content/bridgemoji/1f987.svg | 1 + .../torpreferences/content/bridgemoji/1f988.svg | 1 + .../torpreferences/content/bridgemoji/1f989.svg | 1 + .../torpreferences/content/bridgemoji/1f98a.svg | 1 + .../torpreferences/content/bridgemoji/1f98b.svg | 1 + .../torpreferences/content/bridgemoji/1f98c.svg | 1 + .../torpreferences/content/bridgemoji/1f98e.svg | 1 + .../torpreferences/content/bridgemoji/1f98f.svg | 1 + .../torpreferences/content/bridgemoji/1f992.svg | 1 + .../torpreferences/content/bridgemoji/1f993.svg | 1 + .../torpreferences/content/bridgemoji/1f994.svg | 1 + .../torpreferences/content/bridgemoji/1f995.svg | 1 + .../torpreferences/content/bridgemoji/1f998.svg | 1 + .../torpreferences/content/bridgemoji/1f999.svg | 1 + .../torpreferences/content/bridgemoji/1f99a.svg | 1 + .../torpreferences/content/bridgemoji/1f99c.svg | 1 + .../torpreferences/content/bridgemoji/1f99d.svg | 1 + .../torpreferences/content/bridgemoji/1f99e.svg | 1 + .../torpreferences/content/bridgemoji/1f9a3.svg | 1 + .../torpreferences/content/bridgemoji/1f9a4.svg | 1 + .../torpreferences/content/bridgemoji/1f9a5.svg | 1 + .../torpreferences/content/bridgemoji/1f9a6.svg | 1 + .../torpreferences/content/bridgemoji/1f9a7.svg | 1 + .../torpreferences/content/bridgemoji/1f9a9.svg | 1 + .../torpreferences/content/bridgemoji/1f9ad.svg | 1 + .../torpreferences/content/bridgemoji/1f9c1.svg | 1 + .../torpreferences/content/bridgemoji/1f9c3.svg | 1 + .../torpreferences/content/bridgemoji/1f9c5.svg | 1 + .../torpreferences/content/bridgemoji/1f9c7.svg | 1 + .../torpreferences/content/bridgemoji/1f9c9.svg | 1 + .../torpreferences/content/bridgemoji/1f9d9.svg | 1 + .../torpreferences/content/bridgemoji/1f9da.svg | 1 + .../torpreferences/content/bridgemoji/1f9dc.svg | 1 + .../torpreferences/content/bridgemoji/1f9e0.svg | 1 + .../torpreferences/content/bridgemoji/1f9e2.svg | 1 + .../torpreferences/content/bridgemoji/1f9e6.svg | 1 + .../torpreferences/content/bridgemoji/1f9e9.svg | 1 + .../torpreferences/content/bridgemoji/1f9ea.svg | 1 + .../torpreferences/content/bridgemoji/1f9ec.svg | 1 + .../torpreferences/content/bridgemoji/1f9ed.svg | 1 + .../torpreferences/content/bridgemoji/1f9ee.svg | 1 + .../torpreferences/content/bridgemoji/1f9f2.svg | 1 + .../torpreferences/content/bridgemoji/1f9f5.svg | 1 + .../torpreferences/content/bridgemoji/1f9f9.svg | 1 + .../torpreferences/content/bridgemoji/1fa73.svg | 1 + .../torpreferences/content/bridgemoji/1fa80.svg | 1 + .../torpreferences/content/bridgemoji/1fa81.svg | 1 + .../torpreferences/content/bridgemoji/1fa83.svg | 1 + .../torpreferences/content/bridgemoji/1fa90.svg | 1 + .../torpreferences/content/bridgemoji/1fa91.svg | 1 + .../torpreferences/content/bridgemoji/1fa95.svg | 1 + .../torpreferences/content/bridgemoji/1fa97.svg | 1 + .../torpreferences/content/bridgemoji/1fab6.svg | 1 + .../torpreferences/content/bridgemoji/1fad0.svg | 1 + .../torpreferences/content/bridgemoji/1fad2.svg | 1 + .../torpreferences/content/bridgemoji/1fad6.svg | 1 + .../torpreferences/content/bridgemoji/23f0.svg | 1 + .../torpreferences/content/bridgemoji/2600.svg | 1 + .../torpreferences/content/bridgemoji/2602.svg | 1 + .../torpreferences/content/bridgemoji/2604.svg | 1 + .../torpreferences/content/bridgemoji/260e.svg | 1 + .../torpreferences/content/bridgemoji/2693.svg | 1 + .../torpreferences/content/bridgemoji/2696.svg | 1 + .../torpreferences/content/bridgemoji/26bd.svg | 1 + .../torpreferences/content/bridgemoji/26f2.svg | 1 + .../torpreferences/content/bridgemoji/26f5.svg | 1 + .../torpreferences/content/bridgemoji/2708.svg | 1 + .../torpreferences/content/bridgemoji/270f.svg | 1 + .../torpreferences/content/bridgemoji/2728.svg | 1 + .../torpreferences/content/bridgemoji/2744.svg | 1 + .../torpreferences/content/builtinBridgeDialog.jsm | 113 + .../content/builtinBridgeDialog.xhtml | 31 + .../components/torpreferences/content/check.svg | 3 + .../content/connectionCategory.inc.xhtml | 9 + .../torpreferences/content/connectionPane.js | 1158 +++ .../torpreferences/content/connectionPane.xhtml | 194 + .../content/connectionSettingsDialog.jsm | 397 + .../content/connectionSettingsDialog.xhtml | 60 + .../components/torpreferences/content/network.svg | 6 + .../torpreferences/content/provideBridgeDialog.jsm | 67 + .../content/provideBridgeDialog.xhtml | 21 + .../torpreferences/content/requestBridgeDialog.jsm | 206 + .../content/requestBridgeDialog.xhtml | 35 + .../torpreferences/content/torLogDialog.jsm | 84 + .../torpreferences/content/torLogDialog.xhtml | 23 + .../torpreferences/content/torPreferences.css | 714 ++ .../torpreferences/content/torPreferencesIcon.svg | 8 + browser/components/torpreferences/jar.mn | 22 + browser/components/torpreferences/moz.build | 1 + tools/torbrowser/update_bridgemoiji.py | 115 + 284 files changed, 12646 insertions(+), 68 deletions(-)
diff --git a/browser/components/moz.build b/browser/components/moz.build index 12a2187c638b..0fa76a0e7038 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -58,6 +58,7 @@ DIRS += [ "translation", "uitour", "urlbar", + "torpreferences", ]
DIRS += ["build"] diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml index 0deb010bbefd..c4ada41e3d75 100644 --- a/browser/components/preferences/main.inc.xhtml +++ b/browser/components/preferences/main.inc.xhtml @@ -753,58 +753,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 f0864a5cfff4..ecdf42cd98c8 100644 --- a/browser/components/preferences/main.js +++ b/browser/components/preferences/main.js @@ -329,15 +329,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.initPrimaryBrowserLanguageUI(); @@ -490,11 +481,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 65cf06fcf5c1..5f327eacf513 100644 --- a/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js @@ -14,6 +14,7 @@ /* import-globals-from findInPage.js */ /* import-globals-from /browser/base/content/utilityOverlay.js */ /* import-globals-from /toolkit/content/preferencesBindings.js */ +/* import-globals-from ../torpreferences/content/connectionPane.js */
"use strict";
@@ -219,6 +220,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 f1a8115843a3..cb8716f48a50 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -14,6 +14,7 @@ <?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 [ <!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd"> @@ -166,6 +167,9 @@ <image class="category-icon"/> <label class="category-name" flex="1" data-l10n-id="more-from-moz-title"></label> </richlistitem> + +#include ../torpreferences/content/connectionCategory.inc.xhtml + </richlistbox>
<spacer flex="1"/> @@ -215,6 +219,7 @@ #include sync.inc.xhtml #include experimental.inc.xhtml #include moreFromMozilla.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 000000000000..e63347742ea5 --- /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 000000000000..5411c963ba49 --- /dev/null +++ b/browser/components/torpreferences/content/bridgeQrDialog.xhtml @@ -0,0 +1,25 @@ +<?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 + <html:div id="bridgeQr"> + <html:div id="bridgeQr-target"/> + <html:div id="bridgeQr-onionBox"/> + <html:div id="bridgeQr-onion"/> + </html:div> + </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/bridgemoji-annotations.json b/browser/components/torpreferences/content/bridgemoji-annotations.json new file mode 100644 index 000000000000..faccd10d8f2c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji-annotations.json @@ -0,0 +1,9032 @@ +{ + "ar": { + "👽️": "كائن فضائي", + "🤖": "وجه روبوت", + "🧠": "دماغ", + "👁️": "عين", + "🧙": "سحرة", + "🧚": "جنيّة خيالية", + "🧜": "مخلوق بحري بجسد بشري وذيل سمكة", + "🐵": "وجه قرد", + "🦧": "إنسان الغاب", + "🐶": "وجه كلب", + "🐺": "وجه ذئب", + "🦊": "وجه ثعلب", + "🦝": "راكون", + "🐱": "وجه قطة", + "🦁": "وجه أسد", + "🐯": "وجه نمر", + "🐴": "وجه حصان", + "🦄": "وجه أحادي قرن", + "🦓": "حمار وحشي", + "🦌": "غزال", + "🐮": "وجه بقرة", + "🐷": "وجه خنزير", + "🐗": "خنزير بري", + "🐪": "جمل وحيد السنام", + "🦙": "لاما", + "🦒": "زرافة", + "🐘": "فيل", + "🦣": "ماموث", + "🦏": "وحيد القرن", + "🐭": "وجه فأر", + "🐰": "وجه أرنب", + "🐿️": "سنجاب", + "🦔": "قنفد", + "🦇": "خفاش", + "🐻": "وجه دب", + "🐨": "دب كوالا", + "🦥": "كسلان", + "🦦": "قندس", + "🦘": "كنغر", + "🐥": "كتكوت صغير بوجه للأمام", + "🐦️": "طائر", + "🕊️": "حمامة", + "🦆": "بطة", + "🦉": "بومة", + "🦤": "طائر الدودو", + "🪶": "ريشة", + "🦩": "فلامينغو", + "🦚": "طاووس", + "🦜": "ببغاء", + "🐊": "تمساح", + "🐢": "سلحفاة", + "🦎": "سحلية", + "🐍": "ثعبان", + "🐲": "وجه تنين", + "🦕": "ديناصور صوروبودا", + "🐳": "حوت بنافورة", + "🐬": "دولفين", + "🦭": "فقمة", + "🐟️": "سمكة", + "🐠": "سمكة استوائية", + "🦈": "سمكة قرش", + "🐙": "أخطبوط", + "🐚": "صدفة حلزونية", + "🐌": "حلزون", + "🦋": "فراشة", + "🐛": "بق", + "🐝": "نحلة", + "🐞": "دعسوقة", + "💐": "باقة زهور", + "🌹": "وردة", + "🌺": "كركديه", + "🌻": "دوار الشمس", + "🌷": "زهرة التوليب", + "🌲": "شجرة دائمة الخضرة", + "🌳": "شجرة زائلة الخضرة", + "🌴": "نخلة", + "🌵": "صبار", + "🌿": "عشب", + "🍁": "ورقة شجر القيقب", + "🍇": "عنب", + "🍈": "شمام", + "🍉": "بطيخ", + "🍊": "يوسفي", + "🍋": "ليمون", + "🍌": "موز", + "🍍": "أناناس", + "🥭": "مانجو", + "🍏": "تفاح أخضر", + "🍐": "كمثرى", + "🍑": "خوخ", + "🍒": "كرز", + "🍓": "فراولة", + "🫐": "توت برّي", + "🥝": "كيوي", + "🍅": "طماطم", + "🫒": "زيتون", + "🥥": "جوز الهند", + "🥑": "أفوكادو", + "🍆": "باذنجان", + "🥕": "جزر", + "🌽": "ذرة", + "🌶️": "فلفل حار", + "🥬": "خضار ورقي", + "🥦": "بروكولي", + "🧅": "بصل", + "🍄": "عيش الغراب", + "🥜": "فول سوداني", + "🥐": "كرواسون", + "🥖": "الخبز الفرنسي", + "🥨": "بريتزل", + "🥯": "خبز البيغل", + "🥞": "فطائر", + "🧇": "وافل", + "🍔": "همبرغر", + "🍕": "بيتزا", + "🌭": "هوت دوغ", + "🌮": "تاكو", + "🍿": "فشار", + "🦀": "سرطان", + "🦞": "كركند", + "🍨": "آيس كريم", + "🍩": "كعكة محلاة", + "🍪": "كعكة", + "🎂": "كعكة عيد ميلاد", + "🧁": "كعكة صغيرة", + "🍫": "لوح شوكولاتة", + "🍬": "حلوى", + "🍭": "مصاصة", + "🫖": "إبريق شاي", + "🧃": "علبة عصير", + "🧉": "متة", + "🧭": "بوصلة", + "🏔️": "جبل بقمة ثلجية", + "🌋": "بركان", + "🏕️": "تخييم", + "🏝️": "جزيرة صحراوية", + "🏡": "منزل بحديقة", + "⛲️": "نافورة", + "🎠": "حصان الدوامة", + "🎡": "عجلة دوارة", + "🎢": "قطار ملاهي", + "💈": "محل حلاقة", + "🚆": "قطار", + "🚋": "سيارة ترام", + "🚍️": "حافلة مقتربة", + "🚕": "تاكسي", + "🚗": "سيارة", + "🚚": "شاحنة توصيل طلبات", + "🚜": "جرار", + "🛵": "سكوتر بمحرك", + "🛺": "توكتوك", + "🛴": "سكوتر بدون محرك", + "🛹": "لوح التزلج", + "🛼": "حذاء مع عجلات", + "⚓️": "مرساة", + "⛵️": "مركب شراعي", + "🛶": "زورق", + "🚤": "قارب سريع", + "🚢": "سفينة", + "✈️": "طائرة", + "🚁": "هليكوبتر", + "🚠": "قطار جبلي", + "🛰️": "قمر صناعي", + "🚀": "صاروخ", + "🛸": "صحن طائر", + "⏰": "ساعة منبه", + "🌙": "هلال", + "🌡️": "ترمومتر", + "☀️": "شمس", + "🪐": "كوكب داخل حلقة", + "🌟": "نجم ساطع", + "🌀": "عاصفة", + "🌈": "ألوان الطيف", + "☂️": "شمسية", + "❄️": "كتلة ثلج", + "☄️": "مذنّب", + "🔥": "حريق", + "💧": "قطرة", + "🌊": "موجة", + "🎃": "هالوين", + "✨": "ومضات", + "🎈": "بالون", + "🎉": "فرقعة حفلة", + "🎏": "قصاصات ملونة", + "🎀": "شريط", + "🎁": "هدية ملفوفة", + "🎟️": "تذكرتا دخول", + "🏆️": "كأس", + "⚽️": "كرة قدم", + "🏀": "كرة سلة", + "🏈": "كرة قدم أمريكية", + "🎾": "تنس", + "🥏": "قرص طائر", + "🏓": "تنس الطاولة", + "🏸": "تنس الريشة", + "🤿": "قناع غطس", + "🥌": "حجرة لعبة الكرلنغ", + "🎯": "نشان", + "🪀": "يويو", + "🪁": "طائرة ورقية", + "🔮": "كرة كريستال", + "🎲": "زهر", + "🧩": "أحجية الصور المقطوعة", + "🎨": "لوحة ألوان الرسام", + "🧵": "بكرة خيط", + "👕": "تي شيرت", + "🧦": "جورب", + "👗": "فستان", + "🩳": "شورت", + "🎒": "حقيبة مدرسة", + "👟": "حذاء رياضي", + "👑": "تاج", + "🧢": "قبعة رياضية", + "💄": "أحمر شفاه", + "💍": "خاتم", + "💎": "جوهرة", + "📢": "مكبر صوت", + "🎶": "نوتات موسيقية", + "🎙️": "ميكروفون استوديو", + "📻️": "راديو", + "🎷": "ساكسفون", + "🪗": "أكورديون", + "🎸": "غيتار", + "🎺": "آلة نفخ", + "🎻": "كمان", + "🪕": "آلة بانجو", + "🥁": "طبلة", + "☎️": "هاتف أرضي", + "🔋": "بطارية", + "💿️": "سي دي", + "🧮": "معداد", + "🎬️": "كلاكيت", + "💡": "مصباح مضاء", + "🔦": "ضوء فلاش", + "🏮": "فانوس بورق أحمر", + "📕": "كتاب مغلق", + "🏷️": "ملصق", + "💳️": "بطاقة ائتمان", + "✏️": "قلم رصاص", + "🖌️": "فرشاة ألوان", + "🖍️": "قلم شمع ملون", + "📌": "دبوس ضغط", + "📎": "مشبك ورق", + "🔑": "مفتاح", + "🪃": "خذوف مرتد", + "🏹": "قوس وسهم", + "⚖️": "ميزان", + "🧲": "مغناطيس", + "🧪": "أنبوب اختبار", + "🧬": "الحامض النووي", + "🔬": "ميكروسكوب", + "🔭": "تليسكوب", + "📡": "طبق قمر صناعي", + "🪑": "كرسي", + "🧹": "مكنسة", + "🗿": "تمثال" + }, + "ca": { + "👽️": "alienígena", + "🤖": "robot", + "🧠": "cervell", + "👁️": "ull", + "🧙": "mag", + "🧚": "fada", + "🧜": "persona sirena", + "🐵": "cara de mona", + "🦧": "orangutan", + "🐶": "cara de gos", + "🐺": "llop", + "🦊": "guineu", + "🦝": "os rentador", + "🐱": "cara de gat", + "🦁": "cara de lleó", + "🐯": "cara de tigre", + "🐴": "cara de cavall", + "🦄": "unicorn", + "🦓": "zebra", + "🦌": "cérvol", + "🐮": "cara de vaca", + "🐷": "cara de porc", + "🐗": "senglar", + "🐪": "dromedari", + "🦙": "llama", + "🦒": "girafa", + "🐘": "elefant", + "🦣": "mamut", + "🦏": "rinoceront", + "🐭": "cara de ratolí", + "🐰": "cara de conill", + "🐿️": "esquirol", + "🦔": "eriçó", + "🦇": "ratapinyada", + "🐻": "cara d’os", + "🐨": "coala", + "🦥": "peresós", + "🦦": "llúdria", + "🦘": "cangur", + "🐥": "pollet de cara", + "🐦️": "ocell", + "🕊️": "colom", + "🦆": "ànec", + "🦉": "mussol", + "🦤": "dodo", + "🪶": "ploma", + "🦩": "flamenc", + "🦚": "paó", + "🦜": "lloro", + "🐊": "cocodril", + "🐢": "tortuga", + "🦎": "llangardaix", + "🐍": "serp", + "🐲": "cara de drac", + "🦕": "sauròpode", + "🐳": "balena que treu aigua", + "🐬": "dofí", + "🦭": "foca", + "🐟️": "peix", + "🐠": "peix tropical", + "🦈": "tauró", + "🐙": "pop", + "🐚": "cargol de mar", + "🐌": "cargol", + "🦋": "papallona", + "🐛": "eruga", + "🐝": "abella", + "🐞": "marieta", + "💐": "ram de flors", + "🌹": "rosa", + "🌺": "hibisc", + "🌻": "gira-sol", + "🌷": "tulipa", + "🌲": "arbre de fulla perenne", + "🌳": "arbre de fulla caduca", + "🌴": "palmera", + "🌵": "cactus", + "🌿": "herba", + "🍁": "fulla d’auró", + "🍇": "raïm", + "🍈": "meló", + "🍉": "síndria", + "🍊": "mandarina", + "🍋": "llimona", + "🍌": "plàtan", + "🍍": "pinya", + "🥭": "mango", + "🍏": "poma verda", + "🍐": "pera", + "🍑": "préssec", + "🍒": "cireres", + "🍓": "maduixa", + "🫐": "nabius", + "🥝": "kiwi", + "🍅": "tomàquet", + "🫒": "oliva", + "🥥": "coco", + "🥑": "alvocat", + "🍆": "albergínia", + "🥕": "pastanaga", + "🌽": "panotxa", + "🌶️": "bitxo", + "🥬": "fulla verda", + "🥦": "bròcoli", + "🧅": "ceba", + "🍄": "bolet", + "🥜": "cacauets", + "🥐": "croissant", + "🥖": "baguet", + "🥨": "brètzel", + "🥯": "bagel", + "🥞": "creps americanes", + "🧇": "gofra", + "🍔": "hamburguesa", + "🍕": "pizza", + "🌭": "frankfurt", + "🌮": "taco", + "🍿": "crispetes", + "🦀": "cranc", + "🦞": "llagosta", + "🍨": "gelat", + "🍩": "dònut", + "🍪": "galeta", + "🎂": "pastís d’aniversari", + "🧁": "cupcake", + "🍫": "rajola de xocolata", + "🍬": "caramel", + "🍭": "piruleta", + "🫖": "tetera", + "🧃": "bric", + "🧉": "mate", + "🧭": "brúixola", + "🏔️": "muntanya amb neu al cim", + "🌋": "volcà", + "🏕️": "acampada", + "🏝️": "illa deserta", + "🏡": "casa amb jardí", + "⛲️": "font", + "🎠": "cavallets", + "🎡": "roda de fira", + "🎢": "muntanya russa", + "💈": "barberia", + "🚆": "tren", + "🚋": "vagó de tramvia", + "🚍️": "autobús que arriba", + "🚕": "taxi", + "🚗": "automòbil", + "🚚": "camió de repartiment", + "🚜": "tractor", + "🛵": "escúter", + "🛺": "bicitaxi amb motor", + "🛴": "patinet", + "🛹": "monopatí", + "🛼": "patí de rodes", + "⚓️": "àncora", + "⛵️": "veler", + "🛶": "canoa", + "🚤": "llanxa ràpida", + "🚢": "vaixell", + "✈️": "avió", + "🚁": "helicòpter", + "🚠": "telefèric de muntanya", + "🛰️": "satèl·lit", + "🚀": "coet", + "🛸": "plat volador", + "⏰": "despertador", + "🌙": "lluna creixent", + "🌡️": "termòmetre", + "☀️": "sol", + "🪐": "planeta amb anell", + "🌟": "estrella brillant", + "🌀": "cicló", + "🌈": "arc de Sant Martí", + "☂️": "paraigua", + "❄️": "floc de neu", + "☄️": "cometa", + "🔥": "foc", + "💧": "gota", + "🌊": "onada", + "🎃": "carabassa de Halloween", + "✨": "espurnes", + "🎈": "globus", + "🎉": "tub de confeti", + "🎏": "banderins de carpes", + "🎀": "llaç", + "🎁": "regal embolicat", + "🎟️": "bitllet d’entrada", + "🏆️": "trofeu", + "⚽️": "pilota de futbol", + "🏀": "pilota de bàsquet", + "🏈": "pilota de futbol americà", + "🎾": "tennis", + "🥏": "disc volador", + "🏓": "tennis de taula", + "🏸": "bàdminton", + "🤿": "ulleres de busseig", + "🥌": "pedra de cúrling", + "🎯": "dards", + "🪀": "io-io", + "🪁": "estel", + "🔮": "bola de vidre", + "🎲": "daus", + "🧩": "peces de trencaclosques", + "🎨": "paleta d’artista", + "🧵": "fil", + "👕": "samarreta", + "🧦": "mitjons", + "👗": "vestit", + "🩳": "pantalons curts", + "🎒": "motxilla d’escola", + "👟": "sabatilla de córrer", + "👑": "corona", + "🧢": "gorra", + "💄": "pintallavis", + "💍": "anell", + "💎": "gemma", + "📢": "altaveu de megafonia", + "🎶": "notes musicals", + "🎙️": "micròfon d’estudi", + "📻️": "ràdio", + "🎷": "saxofon", + "🪗": "acordió", + "🎸": "guitarra", + "🎺": "trompeta", + "🎻": "violí", + "🪕": "banjo", + "🥁": "tambor", + "☎️": "telèfon", + "🔋": "bateria", + "💿️": "disc òptic", + "🧮": "àbac", + "🎬️": "claqueta", + "💡": "bombeta", + "🔦": "llanterna", + "🏮": "fanal de paper vermell", + "📕": "llibre tancat", + "🏷️": "etiqueta", + "💳️": "targeta de crèdit", + "✏️": "llapis", + "🖌️": "pinzell", + "🖍️": "llapis de color", + "📌": "xinxeta", + "📎": "clip", + "🔑": "clau", + "🪃": "bumerang", + "🏹": "arc i fletxa", + "⚖️": "balança (aparell)", + "🧲": "imant", + "🧪": "tub d’assaig", + "🧬": "adn", + "🔬": "microscopi", + "🔭": "telescopi", + "📡": "antena de satèl·lit", + "🪑": "cadira", + "🧹": "escombra", + "🗿": "moai" + }, + "cs": { + "👽️": "mimozemšťan", + "🤖": "obličej robota", + "🧠": "mozek", + "👁️": "oko", + "🧙": "mág", + "🧚": "víla", + "🧜": "mořská bytost", + "🐵": "hlava opice", + "🦧": "orangutan", + "🐶": "hlava psa", + "🐺": "hlava vlka", + "🦊": "hlava lišky", + "🦝": "mýval", + "🐱": "hlava kočky", + "🦁": "hlava lva", + "🐯": "hlava tygra", + "🐴": "hlava koně", + "🦄": "hlava jednorožce", + "🦓": "zebra", + "🦌": "jelen", + "🐮": "hlava krávy", + "🐷": "hlava prasete", + "🐗": "kanec", + "🐪": "velbloud jednohrbý", + "🦙": "lama", + "🦒": "žirafa", + "🐘": "slon", + "🦣": "mamut", + "🦏": "nosorožec", + "🐭": "hlava myši", + "🐰": "hlava králíka", + "🐿️": "veverka", + "🦔": "ježek", + "🦇": "netopýr", + "🐻": "hlava medvěda", + "🐨": "koala", + "🦥": "lenochod", + "🦦": "vydra", + "🦘": "klokan", + "🐥": "kuřátko zepředu", + "🐦️": "pták", + "🕊️": "holubice", + "🦆": "kachna", + "🦉": "sova", + "🦤": "dodo", + "🪶": "pírko", + "🦩": "plameňák", + "🦚": "páv", + "🦜": "papoušek", + "🐊": "krokodýl", + "🐢": "želva", + "🦎": "ještěrka", + "🐍": "had", + "🐲": "hlava draka", + "🦕": "brontosaurus", + "🐳": "velryba stříkající vodu", + "🐬": "delfín", + "🦭": "tuleň", + "🐟️": "ryba", + "🐠": "tropická ryba", + "🦈": "žralok", + "🐙": "chobotnice", + "🐚": "ulita", + "🐌": "hlemýžď", + "🦋": "motýl", + "🐛": "housenka", + "🐝": "včela", + "🐞": "beruška", + "💐": "kytice", + "🌹": "růže", + "🌺": "ibišek", + "🌻": "slunečnice", + "🌷": "tulipán", + "🌲": "jehličnatý strom", + "🌳": "listnatý strom", + "🌴": "palma", + "🌵": "kaktus", + "🌿": "bylina", + "🍁": "javorový list", + "🍇": "hrozny", + "🍈": "cukrový meloun", + "🍉": "meloun", + "🍊": "mandarinka", + "🍋": "citron", + "🍌": "banán", + "🍍": "ananas", + "🥭": "mango", + "🍏": "zelené jablko", + "🍐": "hruška", + "🍑": "broskev", + "🍒": "třešně", + "🍓": "jahoda", + "🫐": "borůvky", + "🥝": "kiwi", + "🍅": "rajče", + "🫒": "oliva", + "🥥": "kokos", + "🥑": "avokádo", + "🍆": "lilek", + "🥕": "mrkev", + "🌽": "kukuřičný klas", + "🌶️": "feferonka", + "🥬": "salátové listy", + "🥦": "brokolice", + "🧅": "cibule", + "🍄": "houba", + "🥜": "arašídy", + "🥐": "croissant", + "🥖": "bageta", + "🥨": "preclík", + "🥯": "bagel", + "🥞": "palačinky", + "🧇": "vafle", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "párek v rohlíku", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "krab", + "🦞": "humr", + "🍨": "kopečková zmrzlina", + "🍩": "donut", + "🍪": "koláček", + "🎂": "narozeninový dort", + "🧁": "košíček", + "🍫": "tabulka čokolády", + "🍬": "bonbon", + "🍭": "lízátko", + "🫖": "čajová konvice", + "🧃": "nápoj v krabičce", + "🧉": "maté", + "🧭": "kompas", + "🏔️": "zasněžená hora", + "🌋": "sopka", + "🏕️": "tábor", + "🏝️": "pustý ostrov", + "🏡": "domek se zahradou", + "⛲️": "fontána", + "🎠": "kolotočový kůň", + "🎡": "ruské kolo", + "🎢": "horská dráha", + "💈": "značka holičství", + "🚆": "vlak", + "🚋": "vagón tramvaje", + "🚍️": "přijíždějící autobus", + "🚕": "taxík", + "🚗": "auto", + "🚚": "dodávka", + "🚜": "traktor", + "🛵": "skútr", + "🛺": "autorikša", + "🛴": "koloběžka", + "🛹": "skateboard", + "🛼": "kolečková brusle", + "⚓️": "kotva", + "⛵️": "plachetnice", + "🛶": "kánoe", + "🚤": "motorový člun", + "🚢": "loď", + "✈️": "letadlo", + "🚁": "vrtulník", + "🚠": "horská lanovka", + "🛰️": "satelit", + "🚀": "raketa", + "🛸": "létající talíř", + "⏰": "budík", + "🌙": "srpek měsíce", + "🌡️": "teploměr", + "☀️": "slunce", + "🪐": "planeta s prstencem", + "🌟": "zářící hvězda", + "🌀": "cyklóna", + "🌈": "duha", + "☂️": "deštník", + "❄️": "sněhová vločka", + "☄️": "kometa", + "🔥": "oheň", + "💧": "kapka", + "🌊": "vodní vlna", + "🎃": "dýňová lucerna", + "✨": "jiskry", + "🎈": "balónek", + "🎉": "vystřelovací konfety", + "🎏": "koinobori", + "🎀": "mašle", + "🎁": "zabalený dárek", + "🎟️": "vstupenky", + "🏆️": "trofejní pohár", + "⚽️": "fotbalový míč", + "🏀": "basketbalový míč", + "🏈": "míč na americký fotbal", + "🎾": "tenis", + "🥏": "frisbee", + "🏓": "stolní tenis", + "🏸": "badminton", + "🤿": "potápěčská maska", + "🥌": "curlingový kámen", + "🎯": "přesný zásah do terče", + "🪀": "jojo", + "🪁": "papírový drak", + "🔮": "křišťálová koule", + "🎲": "hrací kostka", + "🧩": "puzzle", + "🎨": "malířská paleta", + "🧵": "cívka nití", + "👕": "tričko", + "🧦": "ponožky", + "👗": "šaty", + "🩳": "šortky", + "🎒": "školní batoh", + "👟": "běžecká bota", + "👑": "koruna", + "🧢": "kšiltovka", + "💄": "rtěnka", + "💍": "prsten", + "💎": "drahokam", + "📢": "tlampač", + "🎶": "noty", + "🎙️": "studiový mikrofon", + "📻️": "rádio", + "🎷": "saxofon", + "🪗": "akordeon", + "🎸": "kytara", + "🎺": "trubka", + "🎻": "housle", + "🪕": "banjo", + "🥁": "buben", + "☎️": "telefon", + "🔋": "baterie", + "💿️": "optický disk", + "🧮": "počitadlo", + "🎬️": "filmová klapka", + "💡": "žárovka", + "🔦": "baterka", + "🏮": "červený lampion", + "📕": "zavřená kniha", + "🏷️": "visačka", + "💳️": "platební karta", + "✏️": "tužka", + "🖌️": "štětec", + "🖍️": "pastelka", + "📌": "připínáček", + "📎": "kancelářská sponka", + "🔑": "klíč", + "🪃": "bumerang", + "🏹": "luk a šíp", + "⚖️": "miskové váhy", + "🧲": "magnet", + "🧪": "zkumavka", + "🧬": "DNA", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "satelitní anténa", + "🪑": "židle", + "🧹": "koště", + "🗿": "socha moai" + }, + "da": { + "👽️": "rumvæsen", + "🤖": "robot", + "🧠": "hjerne", + "👁️": "øje", + "🧙": "magiker", + "🧚": "fe", + "🧜": "havvæsen", + "🐵": "abehoved", + "🦧": "orangutang", + "🐶": "hundehoved", + "🐺": "ulvehoved", + "🦊": "rævehoved", + "🦝": "vaskebjørn", + "🐱": "kattehoved", + "🦁": "løvehoved", + "🐯": "tigerhoved", + "🐴": "hestehoved", + "🦄": "enhjørningehoved", + "🦓": "zebra", + "🦌": "hjort", + "🐮": "kohoved", + "🐷": "grisehoved", + "🐗": "vildsvin", + "🐪": "dromedar", + "🦙": "lama", + "🦒": "giraf", + "🐘": "elefant", + "🦣": "mammut", + "🦏": "næsehorn", + "🐭": "musehoved", + "🐰": "kaninhoved", + "🐿️": "jordegern", + "🦔": "pindsvin", + "🦇": "flagermus", + "🐻": "bjørnehoved", + "🐨": "koala", + "🦥": "dovendyr", + "🦦": "odder", + "🦘": "kænguru", + "🐥": "kylling forfra", + "🐦️": "fugl", + "🕊️": "due", + "🦆": "and", + "🦉": "ugle", + "🦤": "dronte", + "🪶": "fjer", + "🦩": "flamingo", + "🦚": "påfugl", + "🦜": "papegøje", + "🐊": "krokodille", + "🐢": "skildpadde", + "🦎": "firben", + "🐍": "slange", + "🐲": "dragehoved", + "🦕": "sauropod", + "🐳": "hval med vandsprøjt", + "🐬": "delfin", + "🦭": "sæl", + "🐟️": "fisk", + "🐠": "tropisk fisk", + "🦈": "haj", + "🐙": "blæksprutte", + "🐚": "konkylie", + "🐌": "snegl", + "🦋": "sommerfugl", + "🐛": "larve", + "🐝": "honningbi", + "🐞": "mariehøne", + "💐": "blomsterbuket", + "🌹": "rose", + "🌺": "hibiscus", + "🌻": "solsikke", + "🌷": "tulipan", + "🌲": "nåletræ", + "🌳": "løvtræ", + "🌴": "palme", + "🌵": "kaktus", + "🌿": "urt", + "🍁": "ahornblad", + "🍇": "vindruer", + "🍈": "melon", + "🍉": "vandmelon", + "🍊": "appelsin", + "🍋": "citron", + "🍌": "banan", + "🍍": "ananas", + "🥭": "mango", + "🍏": "grønt æble", + "🍐": "pære", + "🍑": "fersken", + "🍒": "kirsebær", + "🍓": "jordbær", + "🫐": "blåbær", + "🥝": "kiwifrugt", + "🍅": "tomat", + "🫒": "oliven", + "🥥": "kokosnød", + "🥑": "avokado", + "🍆": "aubergine", + "🥕": "gulerod", + "🌽": "majs", + "🌶️": "chili", + "🥬": "bladgrønt", + "🥦": "broccoli", + "🧅": "løg", + "🍄": "svamp", + "🥜": "jordnødder", + "🥐": "croissant", + "🥖": "flute", + "🥨": "kringle", + "🥯": "bagel", + "🥞": "pandekager", + "🧇": "vaffel", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hotdog", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "krabbe", + "🦞": "hummer", + "🍨": "is", + "🍩": "donut", + "🍪": "småkage", + "🎂": "fødselsdagskage", + "🧁": "cupcake", + "🍫": "chokolade", + "🍬": "bolsje", + "🍭": "slikkepind", + "🫖": "tekande", + "🧃": "brik", + "🧉": "mate", + "🧭": "kompas", + "🏔️": "sneklædt bjerg", + "🌋": "vulkan", + "🏕️": "camping", + "🏝️": "øde ø", + "🏡": "hus med have", + "⛲️": "springvand", + "🎠": "karrusel", + "🎡": "pariserhjul", + "🎢": "rutsjebane", + "💈": "barber", + "🚆": "tog", + "🚋": "vogn", + "🚍️": "bus forfra", + "🚕": "taxa", + "🚗": "bil", + "🚚": "lastbil", + "🚜": "traktor", + "🛵": "scooter", + "🛺": "motor-rickshaw", + "🛴": "løbehjul", + "🛹": "skateboard", + "🛼": "rulleskøjte", + "⚓️": "anker", + "⛵️": "sejlbåd", + "🛶": "kano", + "🚤": "speedbåd", + "🚢": "skib", + "✈️": "fly", + "🚁": "helikopter", + "🚠": "kabelbane", + "🛰️": "satellit", + "🚀": "raket", + "🛸": "ufo", + "⏰": "vækkeur", + "🌙": "halvmåne", + "🌡️": "termometer", + "☀️": "sol", + "🪐": "planet med ringe", + "🌟": "blinkende stjerne", + "🌀": "cyklon", + "🌈": "regnbue", + "☂️": "paraply", + "❄️": "snefnug", + "☄️": "komet", + "🔥": "ild", + "💧": "dråbe", + "🌊": "bølge", + "🎃": "græskarmand", + "✨": "stjerner", + "🎈": "ballon", + "🎉": "bordbombe", + "🎏": "karpevimpel", + "🎀": "sløjfe", + "🎁": "gave", + "🎟️": "adgangsbillet", + "🏆️": "pokal", + "⚽️": "fodbold", + "🏀": "basketball", + "🏈": "amerikansk fodbold", + "🎾": "tennis", + "🥏": "frisbee", + "🏓": "bordtennis", + "🏸": "badminton", + "🤿": "dykkermaske", + "🥌": "curlingsten", + "🎯": "pletskud", + "🪀": "yoyo", + "🪁": "legetøjsdrage", + "🔮": "krystalkugle", + "🎲": "terning", + "🧩": "brik til puslespil", + "🎨": "palet", + "🧵": "tråd", + "👕": "T-shirt", + "🧦": "sokker", + "👗": "kjole", + "🩳": "shorts", + "🎒": "rygsæk", + "👟": "løbesko", + "👑": "krone", + "🧢": "kasket", + "💄": "læbestift", + "💍": "ring", + "💎": "ædelsten", + "📢": "elektrisk megafon", + "🎶": "noder", + "🎙️": "studiemikrofon", + "📻️": "radio", + "🎷": "saxofon", + "🪗": "akkordeon", + "🎸": "guitar", + "🎺": "trompet", + "🎻": "violin", + "🪕": "banjo", + "🥁": "tromme", + "☎️": "telefon", + "🔋": "batteri", + "💿️": "cd", + "🧮": "kugleramme", + "🎬️": "klaptræ", + "💡": "elpære", + "🔦": "lommelygte", + "🏮": "papirlanterne", + "📕": "lukket bog", + "🏷️": "mærkat", + "💳️": "kreditkort", + "✏️": "blyant", + "🖌️": "malerpensel", + "🖍️": "farvekridt", + "📌": "tegnestift", + "📎": "papirclips", + "🔑": "nøgle", + "🪃": "boomerang", + "🏹": "bue og pil", + "⚖️": "vægt", + "🧲": "magnet", + "🧪": "reagensglas", + "🧬": "dna", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "parabolantenne", + "🪑": "stol", + "🧹": "kost", + "🗿": "statue" + }, + "de": { + "👽️": "Außerirdischer", + "🤖": "Roboter", + "🧠": "Gehirn", + "👁️": "Auge", + "🧙": "Magier(in)", + "🧚": "Märchenfee", + "🧜": "Wassermensch", + "🐵": "Affengesicht", + "🦧": "Orang-Utan", + "🐶": "Hundegesicht", + "🐺": "Wolf", + "🦊": "Fuchs", + "🦝": "Waschbär", + "🐱": "Katzengesicht", + "🦁": "Löwe", + "🐯": "Tigergesicht", + "🐴": "Pferdegesicht", + "🦄": "Einhorn", + "🦓": "Zebra", + "🦌": "Hirsch", + "🐮": "Kuhgesicht", + "🐷": "Schweinegesicht", + "🐗": "Wildschwein", + "🐪": "Dromedar", + "🦙": "Lama", + "🦒": "Giraffe", + "🐘": "Elefant", + "🦣": "Mammut", + "🦏": "Nashorn", + "🐭": "Mäusegesicht", + "🐰": "Hasengesicht", + "🐿️": "Streifenhörnchen", + "🦔": "Igel", + "🦇": "Fledermaus", + "🐻": "Bär", + "🐨": "Koala", + "🦥": "Faultier", + "🦦": "Otter", + "🦘": "Känguru", + "🐥": "Küken von vorne", + "🐦️": "Vogel", + "🕊️": "Taube", + "🦆": "Ente", + "🦉": "Eule", + "🦤": "Dodo", + "🪶": "Feder", + "🦩": "Flamingo", + "🦚": "Pfau", + "🦜": "Papagei", + "🐊": "Krokodil", + "🐢": "Schildkröte", + "🦎": "Eidechse", + "🐍": "Schlange", + "🐲": "Drachengesicht", + "🦕": "Sauropode", + "🐳": "blasender Wal", + "🐬": "Delfin", + "🦭": "Seehund", + "🐟️": "Fisch", + "🐠": "Tropenfisch", + "🦈": "Hai", + "🐙": "Oktopus", + "🐚": "Schneckenhaus", + "🐌": "Schnecke", + "🦋": "Schmetterling", + "🐛": "Raupe", + "🐝": "Biene", + "🐞": "Marienkäfer", + "💐": "Blumenstrauß", + "🌹": "Rose", + "🌺": "Hibiskus", + "🌻": "Sonnenblume", + "🌷": "Tulpe", + "🌲": "Nadelbaum", + "🌳": "Laubbaum", + "🌴": "Palme", + "🌵": "Kaktus", + "🌿": "Kräuter", + "🍁": "Ahornblatt", + "🍇": "Trauben", + "🍈": "Honigmelone", + "🍉": "Wassermelone", + "🍊": "Mandarine", + "🍋": "Zitrone", + "🍌": "Banane", + "🍍": "Ananas", + "🥭": "Mango", + "🍏": "grüner Apfel", + "🍐": "Birne", + "🍑": "Pfirsich", + "🍒": "Kirschen", + "🍓": "Erdbeere", + "🫐": "Blaubeeren", + "🥝": "Kiwi", + "🍅": "Tomate", + "🫒": "Olive", + "🥥": "Kokosnuss", + "🥑": "Avocado", + "🍆": "Aubergine", + "🥕": "Karotte", + "🌽": "Maiskolben", + "🌶️": "Peperoni", + "🥬": "Blattgemüse", + "🥦": "Brokkoli", + "🧅": "Zwiebel", + "🍄": "Fliegenpilz", + "🥜": "Erdnuss", + "🥐": "Croissant", + "🥖": "Baguette", + "🥨": "Brezel", + "🥯": "Bagel", + "🥞": "Pfannkuchen", + "🧇": "Waffel", + "🍔": "Hamburger", + "🍕": "Pizza", + "🌭": "Hotdog", + "🌮": "Taco", + "🍿": "Popcorn", + "🦀": "Krebs", + "🦞": "Hummer", + "🍨": "Eiscreme", + "🍩": "Donut", + "🍪": "Keks", + "🎂": "Geburtstagskuchen", + "🧁": "Cupcake", + "🍫": "Schokoladentafel", + "🍬": "Bonbon", + "🍭": "Lutscher", + "🫖": "Teekanne", + "🧃": "Trinkpäckchen", + "🧉": "Mate-Tee", + "🧭": "Kompass", + "🏔️": "schneebedeckter Berg", + "🌋": "Vulkan", + "🏕️": "Camping", + "🏝️": "einsame Insel", + "🏡": "Haus mit Garten", + "⛲️": "Springbrunnen", + "🎠": "Karussellpferd", + "🎡": "Riesenrad", + "🎢": "Achterbahn", + "💈": "Barbershop-Säule", + "🚆": "Zug", + "🚋": "Straßenbahnwagen", + "🚍️": "Bus von vorne", + "🚕": "Taxi", + "🚗": "Auto", + "🚚": "Lieferwagen", + "🚜": "Traktor", + "🛵": "Motorroller", + "🛺": "Autorikscha", + "🛴": "Tretroller", + "🛹": "Skateboard", + "🛼": "Rollschuh", + "⚓️": "Anker", + "⛵️": "Segelboot", + "🛶": "Kanu", + "🚤": "Schnellboot", + "🚢": "Schiff", + "✈️": "Flugzeug", + "🚁": "Hubschrauber", + "🚠": "Bergschwebebahn", + "🛰️": "Satellit", + "🚀": "Rakete", + "🛸": "fliegende Untertasse", + "⏰": "Wecker", + "🌙": "Mondsichel", + "🌡️": "Thermometer", + "☀️": "Sonne", + "🪐": "Ringplanet", + "🌟": "funkelnder Stern", + "🌀": "Wirbel", + "🌈": "Regenbogen", + "☂️": "Regenschirm", + "❄️": "Schneeflocke", + "☄️": "Komet", + "🔥": "Feuer", + "💧": "Tropfen", + "🌊": "Welle", + "🎃": "Halloweenkürbis", + "✨": "funkelnde Sterne", + "🎈": "Luftballon", + "🎉": "Konfettibombe", + "🎏": "traditionelle japanische Windsäcke", + "🎀": "pinke Schleife", + "🎁": "Geschenk", + "🎟️": "Eintrittskarten", + "🏆️": "Pokal", + "⚽️": "Fußball", + "🏀": "Basketball", + "🏈": "Football", + "🎾": "Tennisball", + "🥏": "Frisbee", + "🏓": "Tischtennis", + "🏸": "Badminton", + "🤿": "Tauchmaske", + "🥌": "Curlingstein", + "🎯": "Darts", + "🪀": "Jo-Jo", + "🪁": "Drachen", + "🔮": "Kristallkugel", + "🎲": "Spielwürfel", + "🧩": "Puzzleteil", + "🎨": "Mischpalette", + "🧵": "Faden", + "👕": "T-Shirt", + "🧦": "Socken", + "👗": "Kleid", + "🩳": "Shorts", + "🎒": "Schulranzen", + "👟": "Sportschuh", + "👑": "Krone", + "🧢": "Baseballmütze", + "💄": "Lippenstift", + "💍": "Ring", + "💎": "Edelstein", + "📢": "Lautsprecher", + "🎶": "Musiknoten", + "🎙️": "Studiomikrofon", + "📻️": "Radio", + "🎷": "Saxofon", + "🪗": "Akkordeon", + "🎸": "Gitarre", + "🎺": "Trompete", + "🎻": "Geige", + "🪕": "Banjo", + "🥁": "Trommel", + "☎️": "Telefon", + "🔋": "Batterie", + "💿️": "CD", + "🧮": "Abakus", + "🎬️": "Filmklappe", + "💡": "Glühbirne", + "🔦": "Taschenlampe", + "🏮": "rote Papierlaterne", + "📕": "geschlossenes Buch", + "🏷️": "Etikett", + "💳️": "Kreditkarte", + "✏️": "Bleistift", + "🖌️": "Pinsel", + "🖍️": "Wachsmalstift", + "📌": "Reißzwecke", + "📎": "Büroklammer", + "🔑": "Schlüssel", + "🪃": "Bumerang", + "🏹": "Pfeil und Bogen", + "⚖️": "Waage", + "🧲": "Magnet", + "🧪": "Reagenzglas", + "🧬": "DNA", + "🔬": "Mikroskop", + "🔭": "Teleskop", + "📡": "Satellitenschüssel", + "🪑": "Stuhl", + "🧹": "Besen", + "🗿": "Statue" + }, + "el": { + "👽️": "εξωγήινος", + "🤖": "ρομπότ", + "🧠": "μυαλό", + "👁️": "μάτι", + "🧙": "μάγος", + "🧚": "νεράιδα", + "🧜": "γοργόνα", + "🐵": "πρόσωπο μαϊμούς", + "🦧": "ουρακοτάγκος", + "🐶": "πρόσωπο σκύλου", + "🐺": "λύκος", + "🦊": "αλεπού", + "🦝": "ρακούν", + "🐱": "πρόσωπο γάτας", + "🦁": "λιοντάρι", + "🐯": "πρόσωπο τίγρης", + "🐴": "πρόσωπο αλόγου", + "🦄": "μονόκερος", + "🦓": "ζέβρα", + "🦌": "ελάφι", + "🐮": "πρόσωπο αγελάδας", + "🐷": "πρόσωπο γουρουνιού", + "🐗": "αγριογούρουνο", + "🐪": "καμήλα", + "🦙": "λάμα", + "🦒": "καμηλοπάρδαλη", + "🐘": "ελέφαντας", + "🦣": "μαμούθ", + "🦏": "ρινόκερος", + "🐭": "πρόσωπο ποντικιού", + "🐰": "πρόσωπο κουνελιού", + "🐿️": "σκιουράκι", + "🦔": "σκαντζόχοιρος", + "🦇": "νυχτερίδα", + "🐻": "αρκούδα", + "🐨": "κοάλα", + "🦥": "βραδύπους", + "🦦": "βίδρα", + "🦘": "καγκουρό", + "🐥": "κοτοπουλάκι που κοιτά μπροστά", + "🐦️": "πουλί", + "🕊️": "περιστέρι", + "🦆": "πάπια", + "🦉": "κουκουβάγια", + "🦤": "ντόντο", + "🪶": "πούπουλο", + "🦩": "φλαμίνγκο", + "🦚": "παγώνι", + "🦜": "παπαγάλος", + "🐊": "κροκόδειλος", + "🐢": "χελώνα", + "🦎": "σαύρα", + "🐍": "φίδι", + "🐲": "πρόσωπο δράκου", + "🦕": "σαυρόποδο", + "🐳": "φάλαινα που πετά νερό", + "🐬": "δελφίνι", + "🦭": "φώκια", + "🐟️": "ψάρι", + "🐠": "τροπικό ψάρι", + "🦈": "καρχαρίας", + "🐙": "χταπόδι", + "🐚": "σπειροειδές κοχύλι", + "🐌": "σαλιγκάρι", + "🦋": "πεταλούδα", + "🐛": "κάμπια", + "🐝": "μέλισσα", + "🐞": "πασχαλίτσα", + "💐": "μπουκέτο λουλούδια", + "🌹": "τριαντάφυλλο", + "🌺": "ιβίσκος", + "🌻": "ηλιοτρόπιο", + "🌷": "τουλίπα", + "🌲": "αειθαλές δέντρο", + "🌳": "φυλλοβόλο δέντρο", + "🌴": "φοίνικας", + "🌵": "κάκτος", + "🌿": "βότανο", + "🍁": "φύλλο σφενδάμου", + "🍇": "σταφύλι", + "🍈": "πεπόνι", + "🍉": "καρπούζι", + "🍊": "μανταρίνι", + "🍋": "λεμόνι", + "🍌": "μπανάνα", + "🍍": "ανανάς", + "🥭": "μάνγκο", + "🍏": "πράσινο μήλο", + "🍐": "αχλάδι", + "🍑": "ροδάκινο", + "🍒": "κεράσια", + "🍓": "φράουλα", + "🫐": "μύρτιλο", + "🥝": "ακτινίδιο", + "🍅": "ντομάτα", + "🫒": "ελιά", + "🥥": "καρύδα", + "🥑": "αβοκάντο", + "🍆": "μελιτζάνα", + "🥕": "καρότο", + "🌽": "καλαμπόκι", + "🌶️": "καυτερή πιπεριά", + "🥬": "πράσινο φυλλώδες", + "🥦": "μπρόκολο", + "🧅": "κρεμμύδι", + "🍄": "μανιτάρι", + "🥜": "φιστίκια", + "🥐": "κρουασάν", + "🥖": "μπαγκέτα", + "🥨": "πρέτσελ", + "🥯": "μπέιγκελ", + "🥞": "τηγανίτες", + "🧇": "βάφλα", + "🍔": "χάμπουργκερ", + "🍕": "πίτσα", + "🌭": "χοτ ντογκ", + "🌮": "τάκο", + "🍿": "ποπ-κορν", + "🦀": "κάβουρας", + "🦞": "αστακός", + "🍨": "παγωτό", + "🍩": "ντόνατ", + "🍪": "μπισκότο", + "🎂": "τούρτα γενεθλίων", + "🧁": "κεκάκι", + "🍫": "σοκολάτα", + "🍬": "καραμέλα", + "🍭": "γλειφιτζούρι", + "🫖": "τσαγιέρα", + "🧃": "κουτί ροφήματος", + "🧉": "μάτε", + "🧭": "πυξίδα", + "🏔️": "χιονισμένο βουνό", + "🌋": "ηφαίστειο", + "🏕️": "κατασκήνωση", + "🏝️": "ερημονήσι", + "🏡": "σπίτι με κήπο", + "⛲️": "σιντριβάνι", + "🎠": "αλογάκι καρουσέλ", + "🎡": "ρόδα λούνα-παρκ", + "🎢": "τρενάκι", + "💈": "σύμβολο κουρείου", + "🚆": "τρένο", + "🚋": "βαγόνι τραμ", + "🚍️": "διερχόμενο λεωφορείο", + "🚕": "ταξί", + "🚗": "αυτοκίνητο", + "🚚": "φορτηγό", + "🚜": "τρακτέρ", + "🛵": "σκουτεράκι", + "🛺": "ποδήλατο-ταξί", + "🛴": "πατίνι", + "🛹": "σκέιτμπορντ", + "🛼": "πατίνια", + "⚓️": "άγκυρα", + "⛵️": "ιστιοφόρο", + "🛶": "κανό", + "🚤": "ταχύπλοο", + "🚢": "πλοίο", + "✈️": "αεροπλάνο", + "🚁": "ελικόπτερο", + "🚠": "τελεφερίκ", + "🛰️": "δορυφόρος", + "🚀": "πύραυλος", + "🛸": "ιπτάμενος δίσκος", + "⏰": "ξυπνητήρι", + "🌙": "ημισέληνος", + "🌡️": "θερμόμετρο", + "☀️": "ήλιος", + "🪐": "πλανήτης με δακτύλιο", + "🌟": "λαμπερό αστέρι", + "🌀": "κυκλώνας", + "🌈": "ουράνιο τόξο", + "☂️": "ομπρέλα", + "❄️": "χιονονιφάδα", + "☄️": "κομήτης", + "🔥": "φωτιά", + "💧": "σταγόνα", + "🌊": "θαλάσσιο κύμα", + "🎃": "φανάρι από κολοκύθα", + "✨": "αστράκια", + "🎈": "μπαλόνι", + "🎉": "χαρτοπόλεμος", + "🎏": "διακοσμητικοί κυπρίνοι", + "🎀": "κορδέλα", + "🎁": "τυλιγμένο δώρο", + "🎟️": "κάρτες εισόδου", + "🏆️": "τρόπαιο", + "⚽️": "μπάλα ποδοσφαίρου", + "🏀": "μπάλα μπάσκετ", + "🏈": "μπάλα αμερικάνικου ποδοσφαίρου", + "🎾": "μπάλα τένις", + "🥏": "φρίσμπι", + "🏓": "πινγκ πονγκ", + "🏸": "μπάντμιντον", + "🤿": "μάσκα κατάδυσης", + "🥌": "πέτρα κέρλινγκ", + "🎯": "βελάκια", + "🪀": "γιογιό", + "🪁": "χαρταετός", + "🔮": "κρυστάλλινη σφαίρα", + "🎲": "ζάρι", + "🧩": "κομμάτι παζλ", + "🎨": "παλέτα ζωγράφου", + "🧵": "κλωστή", + "👕": "μπλουζάκι", + "🧦": "κάλτσες", + "👗": "φόρεμα", + "🩳": "σορτσάκι", + "🎒": "σχολική σάκα", + "👟": "αθλητικό παπούτσι", + "👑": "στέμμα", + "🧢": "καπελάκι", + "💄": "κραγιόν", + "💍": "δαχτυλίδι", + "💎": "διαμάντι", + "📢": "ντουντούκα", + "🎶": "νότες μουσικής", + "🎙️": "μικρόφωνο στούντιο", + "📻️": "ραδιόφωνο", + "🎷": "σαξόφωνο", + "🪗": "ακορντεόν", + "🎸": "κιθάρα", + "🎺": "τρομπέτα", + "🎻": "βιολί", + "🪕": "μπάντζο", + "🥁": "τύμπανο", + "☎️": "τηλέφωνο", + "🔋": "μπαταρία", + "💿️": "οπτικός δίσκος", + "🧮": "άβακας", + "🎬️": "κλακέτα φιλμ", + "💡": "λαμπτήρας", + "🔦": "φακός", + "🏮": "κόκκινο χάρτινο φανάρι", + "📕": "κλειστό βιβλίο", + "🏷️": "ετικέτα", + "💳️": "πιστωτική κάρτα", + "✏️": "μολύβι", + "🖌️": "πινέλο", + "🖍️": "κηρομπογιά", + "📌": "πινέζα", + "📎": "συνδετήρας", + "🔑": "κλειδί", + "🪃": "μπούμερανγκ", + "🏹": "τόξο και βέλος", + "⚖️": "ζυγαριά", + "🧲": "μαγνήτης", + "🧪": "δοκιμαστικός σωλήνας", + "🧬": "dna", + "🔬": "μικροσκόπιο", + "🔭": "τηλεσκόπιο", + "📡": "δορυφορική κεραία", + "🪑": "καρέκλα", + "🧹": "σκούπα", + "🗿": "μοάι" + }, + "en": { + "👽️": "alien", + "🤖": "robot", + "🧠": "brain", + "👁️": "eye", + "🧙": "mage", + "🧚": "fairy", + "🧜": "merperson", + "🐵": "monkey face", + "🦧": "orangutan", + "🐶": "dog face", + "🐺": "wolf", + "🦊": "fox", + "🦝": "raccoon", + "🐱": "cat face", + "🦁": "lion", + "🐯": "tiger face", + "🐴": "horse face", + "🦄": "unicorn", + "🦓": "zebra", + "🦌": "deer", + "🐮": "cow face", + "🐷": "pig face", + "🐗": "boar", + "🐪": "camel", + "🦙": "llama", + "🦒": "giraffe", + "🐘": "elephant", + "🦣": "mammoth", + "🦏": "rhinoceros", + "🐭": "mouse face", + "🐰": "rabbit face", + "🐿️": "chipmunk", + "🦔": "hedgehog", + "🦇": "bat", + "🐻": "bear", + "🐨": "koala", + "🦥": "sloth", + "🦦": "otter", + "🦘": "kangaroo", + "🐥": "front-facing baby chick", + "🐦️": "bird", + "🕊️": "dove", + "🦆": "duck", + "🦉": "owl", + "🦤": "dodo", + "🪶": "feather", + "🦩": "flamingo", + "🦚": "peacock", + "🦜": "parrot", + "🐊": "crocodile", + "🐢": "turtle", + "🦎": "lizard", + "🐍": "snake", + "🐲": "dragon face", + "🦕": "sauropod", + "🐳": "spouting whale", + "🐬": "dolphin", + "🦭": "seal", + "🐟️": "fish", + "🐠": "tropical fish", + "🦈": "shark", + "🐙": "octopus", + "🐚": "spiral shell", + "🐌": "snail", + "🦋": "butterfly", + "🐛": "bug", + "🐝": "honeybee", + "🐞": "lady beetle", + "💐": "bouquet", + "🌹": "rose", + "🌺": "hibiscus", + "🌻": "sunflower", + "🌷": "tulip", + "🌲": "evergreen tree", + "🌳": "deciduous tree", + "🌴": "palm tree", + "🌵": "cactus", + "🌿": "herb", + "🍁": "maple leaf", + "🍇": "grapes", + "🍈": "melon", + "🍉": "watermelon", + "🍊": "tangerine", + "🍋": "lemon", + "🍌": "banana", + "🍍": "pineapple", + "🥭": "mango", + "🍏": "green apple", + "🍐": "pear", + "🍑": "peach", + "🍒": "cherries", + "🍓": "strawberry", + "🫐": "blueberries", + "🥝": "kiwi fruit", + "🍅": "tomato", + "🫒": "olive", + "🥥": "coconut", + "🥑": "avocado", + "🍆": "eggplant", + "🥕": "carrot", + "🌽": "ear of corn", + "🌶️": "hot pepper", + "🥬": "leafy green", + "🥦": "broccoli", + "🧅": "onion", + "🍄": "mushroom", + "🥜": "peanuts", + "🥐": "croissant", + "🥖": "baguette bread", + "🥨": "pretzel", + "🥯": "bagel", + "🥞": "pancakes", + "🧇": "waffle", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "crab", + "🦞": "lobster", + "🍨": "ice cream", + "🍩": "doughnut", + "🍪": "cookie", + "🎂": "birthday cake", + "🧁": "cupcake", + "🍫": "chocolate bar", + "🍬": "candy", + "🍭": "lollipop", + "🫖": "teapot", + "🧃": "beverage box", + "🧉": "mate", + "🧭": "compass", + "🏔️": "snow-capped mountain", + "🌋": "volcano", + "🏕️": "camping", + "🏝️": "desert island", + "🏡": "house with garden", + "⛲️": "fountain", + "🎠": "carousel horse", + "🎡": "ferris wheel", + "🎢": "roller coaster", + "💈": "barber pole", + "🚆": "train", + "🚋": "tram car", + "🚍️": "oncoming bus", + "🚕": "taxi", + "🚗": "automobile", + "🚚": "delivery truck", + "🚜": "tractor", + "🛵": "motor scooter", + "🛺": "auto rickshaw", + "🛴": "kick scooter", + "🛹": "skateboard", + "🛼": "roller skate", + "⚓️": "anchor", + "⛵️": "sailboat", + "🛶": "canoe", + "🚤": "speedboat", + "🚢": "ship", + "✈️": "airplane", + "🚁": "helicopter", + "🚠": "mountain cableway", + "🛰️": "satellite", + "🚀": "rocket", + "🛸": "flying saucer", + "⏰": "alarm clock", + "🌙": "crescent moon", + "🌡️": "thermometer", + "☀️": "sun", + "🪐": "ringed planet", + "🌟": "glowing star", + "🌀": "cyclone", + "🌈": "rainbow", + "☂️": "umbrella", + "❄️": "snowflake", + "☄️": "comet", + "🔥": "fire", + "💧": "droplet", + "🌊": "water wave", + "🎃": "jack-o-lantern", + "✨": "sparkles", + "🎈": "balloon", + "🎉": "party popper", + "🎏": "carp streamer", + "🎀": "ribbon", + "🎁": "wrapped gift", + "🎟️": "admission tickets", + "🏆️": "trophy", + "⚽️": "soccer ball", + "🏀": "basketball", + "🏈": "american football", + "🎾": "tennis", + "🥏": "flying disc", + "🏓": "ping pong", + "🏸": "badminton", + "🤿": "diving mask", + "🥌": "curling stone", + "🎯": "bullseye", + "🪀": "yo-yo", + "🪁": "kite", + "🔮": "crystal ball", + "🎲": "game die", + "🧩": "puzzle piece", + "🎨": "artist palette", + "🧵": "thread", + "👕": "t-shirt", + "🧦": "socks", + "👗": "dress", + "🩳": "shorts", + "🎒": "backpack", + "👟": "running shoe", + "👑": "crown", + "🧢": "billed cap", + "💄": "lipstick", + "💍": "ring", + "💎": "gem stone", + "📢": "loudspeaker", + "🎶": "musical notes", + "🎙️": "studio microphone", + "📻️": "radio", + "🎷": "saxophone", + "🪗": "accordion", + "🎸": "guitar", + "🎺": "trumpet", + "🎻": "violin", + "🪕": "banjo", + "🥁": "drum", + "☎️": "telephone", + "🔋": "battery", + "💿️": "optical disk", + "🧮": "abacus", + "🎬️": "clapper board", + "💡": "light bulb", + "🔦": "flashlight", + "🏮": "red paper lantern", + "📕": "closed book", + "🏷️": "label", + "💳️": "credit card", + "✏️": "pencil", + "🖌️": "paintbrush", + "🖍️": "crayon", + "📌": "pushpin", + "📎": "paperclip", + "🔑": "key", + "🪃": "boomerang", + "🏹": "bow and arrow", + "⚖️": "balance scale", + "🧲": "magnet", + "🧪": "test tube", + "🧬": "dna", + "🔬": "microscope", + "🔭": "telescope", + "📡": "satellite antenna", + "🪑": "chair", + "🧹": "broom", + "🗿": "moai" + }, + "es": { + "👽️": "alienígena", + "🤖": "robot", + "🧠": "cerebro", + "👁️": "ojo", + "🧙": "persona maga", + "🧚": "hada", + "🧜": "persona sirena", + "🐵": "cara de mono", + "🦧": "orangután", + "🐶": "cara de perro", + "🐺": "lobo", + "🦊": "zorro", + "🦝": "mapache", + "🐱": "cara de gato", + "🦁": "león", + "🐯": "cara de tigre", + "🐴": "cara de caballo", + "🦄": "unicornio", + "🦓": "cebra", + "🦌": "ciervo", + "🐮": "cara de vaca", + "🐷": "cara de cerdo", + "🐗": "jabalí", + "🐪": "dromedario", + "🦙": "llama", + "🦒": "jirafa", + "🐘": "elefante", + "🦣": "mamut", + "🦏": "rinoceronte", + "🐭": "cara de ratón", + "🐰": "cara de conejo", + "🐿️": "ardilla", + "🦔": "erizo", + "🦇": "murciélago", + "🐻": "oso", + "🐨": "koala", + "🦥": "perezoso", + "🦦": "nutria", + "🦘": "canguro", + "🐥": "pollito de frente", + "🐦️": "pájaro", + "🕊️": "paloma", + "🦆": "pato", + "🦉": "búho", + "🦤": "dodo", + "🪶": "pluma", + "🦩": "flamenco", + "🦚": "pavo real", + "🦜": "loro", + "🐊": "cocodrilo", + "🐢": "tortuga", + "🦎": "lagarto", + "🐍": "serpiente", + "🐲": "cara de dragón", + "🦕": "saurópodo", + "🐳": "ballena soltando un chorro", + "🐬": "delfín", + "🦭": "foca", + "🐟️": "pez", + "🐠": "pez tropical", + "🦈": "tiburón", + "🐙": "pulpo", + "🐚": "concha de mar", + "🐌": "caracol", + "🦋": "mariposa", + "🐛": "bicho", + "🐝": "abeja", + "🐞": "mariquita", + "💐": "ramo de flores", + "🌹": "rosa", + "🌺": "flor de hibisco", + "🌻": "girasol", + "🌷": "tulipán", + "🌲": "árbol de hoja perenne", + "🌳": "árbol de hoja caduca", + "🌴": "palmera", + "🌵": "cactus", + "🌿": "hierba", + "🍁": "hoja de arce", + "🍇": "uvas", + "🍈": "melón", + "🍉": "sandía", + "🍊": "mandarina", + "🍋": "limón", + "🍌": "plátano", + "🍍": "piña", + "🥭": "mango", + "🍏": "manzana verde", + "🍐": "pera", + "🍑": "melocotón", + "🍒": "cerezas", + "🍓": "fresa", + "🫐": "arándanos", + "🥝": "kiwi", + "🍅": "tomate", + "🫒": "aceituna", + "🥥": "coco", + "🥑": "aguacate", + "🍆": "berenjena", + "🥕": "zanahoria", + "🌽": "espiga de maíz", + "🌶️": "chile picante", + "🥬": "verdura de hoja verde", + "🥦": "brócoli", + "🧅": "cebolla", + "🍄": "champiñón", + "🥜": "cacahuetes", + "🥐": "cruasán", + "🥖": "baguete", + "🥨": "bretzel", + "🥯": "bagel", + "🥞": "tortitas", + "🧇": "gofre", + "🍔": "hamburguesa", + "🍕": "pizza", + "🌭": "perrito caliente", + "🌮": "taco", + "🍿": "palomitas", + "🦀": "cangrejo", + "🦞": "bogavante", + "🍨": "helado", + "🍩": "dónut", + "🍪": "galleta", + "🎂": "tarta de cumpleaños", + "🧁": "magdalena", + "🍫": "tableta de chocolate", + "🍬": "caramelo", + "🍭": "piruleta", + "🫖": "tetera", + "🧃": "tetrabrik", + "🧉": "mate", + "🧭": "brújula", + "🏔️": "montaña con nieve", + "🌋": "volcán", + "🏕️": "camping", + "🏝️": "isla desierta", + "🏡": "casa con jardín", + "⛲️": "fuente", + "🎠": "caballo de tiovivo", + "🎡": "noria de feria", + "🎢": "montaña rusa", + "💈": "poste de barbero", + "🚆": "tren", + "🚋": "vagón de tranvía", + "🚍️": "autobús próximo", + "🚕": "taxi", + "🚗": "coche", + "🚚": "camión de reparto", + "🚜": "tractor", + "🛵": "scooter", + "🛺": "mototaxi", + "🛴": "patinete", + "🛹": "monopatín", + "🛼": "patines", + "⚓️": "ancla", + "⛵️": "velero", + "🛶": "canoa", + "🚤": "lancha motora", + "🚢": "barco", + "✈️": "avión", + "🚁": "helicóptero", + "🚠": "teleférico de montaña", + "🛰️": "satélite", + "🚀": "cohete", + "🛸": "platillo volante", + "⏰": "reloj despertador", + "🌙": "luna", + "🌡️": "termómetro", + "☀️": "sol", + "🪐": "planeta con anillos", + "🌟": "estrella brillante", + "🌀": "ciclón", + "🌈": "arcoíris", + "☂️": "paraguas", + "❄️": "copo de nieve", + "☄️": "meteorito", + "🔥": "fuego", + "💧": "gota", + "🌊": "ola de mar", + "🎃": "calabaza de Halloween", + "✨": "chispas", + "🎈": "globo", + "🎉": "cañón de confeti", + "🎏": "banderín de carpas", + "🎀": "lazo", + "🎁": "regalo", + "🎟️": "entradas", + "🏆️": "trofeo", + "⚽️": "balón de fútbol", + "🏀": "balón de baloncesto", + "🏈": "balón de fútbol americano", + "🎾": "pelota de tenis", + "🥏": "disco volador", + "🏓": "tenis de mesa", + "🏸": "bádminton", + "🤿": "máscara de buceo", + "🥌": "piedra de curling", + "🎯": "diana", + "🪀": "yoyó", + "🪁": "cometa", + "🔮": "bola de cristal", + "🎲": "dado", + "🧩": "pieza de puzle", + "🎨": "paleta de pintor", + "🧵": "hilo", + "👕": "camiseta", + "🧦": "calcetines", + "👗": "vestido", + "🩳": "pantalones cortos", + "🎒": "mochila escolar", + "👟": "zapatilla deportiva", + "👑": "corona", + "🧢": "gorra con visera", + "💄": "pintalabios", + "💍": "anillo", + "💎": "piedra preciosa", + "📢": "altavoz de mano", + "🎶": "notas musicales", + "🎙️": "micrófono de estudio", + "📻️": "radio", + "🎷": "saxofón", + "🪗": "acordeón", + "🎸": "guitarra", + "🎺": "trompeta", + "🎻": "violín", + "🪕": "banjo", + "🥁": "tambor", + "☎️": "teléfono", + "🔋": "pila", + "💿️": "disco óptico", + "🧮": "ábaco", + "🎬️": "claqueta", + "💡": "bombilla", + "🔦": "linterna", + "🏮": "lámpara japonesa", + "📕": "libro cerrado", + "🏷️": "etiqueta", + "💳️": "tarjeta de crédito", + "✏️": "lápiz", + "🖌️": "pincel", + "🖍️": "lápiz de cera", + "📌": "chincheta", + "📎": "clip", + "🔑": "llave", + "🪃": "bumerán", + "🏹": "arco y flecha", + "⚖️": "balanza", + "🧲": "imán", + "🧪": "tubo de ensayo", + "🧬": "adn", + "🔬": "microscopio", + "🔭": "telescopio", + "📡": "antena de satélite", + "🪑": "silla", + "🧹": "escoba", + "🗿": "estatua moái" + }, + "fa": { + "👽️": "موجود فضایی", + "🤖": "روبات", + "🧠": "مغز", + "👁️": "چشم", + "🧙": "ساحر", + "🧚": "پری", + "🧜": "دریامردمان", + "🐵": "صورت میمون", + "🦧": "اورانگوتان", + "🐶": "صورت سگ", + "🐺": "گرگ", + "🦊": "روباه", + "🦝": "راکون", + "🐱": "صورت گربه", + "🦁": "شیر", + "🐯": "صورت ببر", + "🐴": "صورت اسب", + "🦄": "تکشاخ", + "🦓": "گورخر", + "🦌": "گوزن", + "🐮": "صورت گاو", + "🐷": "صورت خوک", + "🐗": "گراز نر", + "🐪": "شتر", + "🦙": "لاما", + "🦒": "زرافه", + "🐘": "فیل", + "🦣": "ماموت", + "🦏": "کرگدن", + "🐭": "صورت موش", + "🐰": "صورت خرگوش", + "🐿️": "سنجاب راهراه", + "🦔": "جوجهتیغی", + "🦇": "خفاش", + "🐻": "خرس", + "🐨": "کوآلا", + "🦥": "تنبل", + "🦦": "سمور آبی", + "🦘": "کانگورو", + "🐥": "جوجه از روبرو", + "🐦️": "پرنده", + "🕊️": "کبوتر", + "🦆": "مرغابی", + "🦉": "جغد", + "🦤": "دودو", + "🪶": "پَر", + "🦩": "فلامینگو", + "🦚": "طاووس", + "🦜": "طوطی", + "🐊": "تمساح", + "🐢": "لاکپشت", + "🦎": "مارمولک", + "🐍": "مار", + "🐲": "صورت اژدها", + "🦕": "سوسمار", + "🐳": "نهنگ در حال آبفشانی", + "🐬": "دلفین", + "🦭": "فُک", + "🐟️": "ماهی", + "🐠": "ماهی استوایی", + "🦈": "کوسه", + "🐙": "هشتپا", + "🐚": "صدف مارپیچی", + "🐌": "حلزون", + "🦋": "پروانه", + "🐛": "حشره", + "🐝": "زنبور عسل", + "🐞": "کفشدوزک", + "💐": "دسته گل", + "🌹": "رز", + "🌺": "گل ختمی", + "🌻": "گل آفتابگردان", + "🌷": "لاله", + "🌲": "همیشهسبز", + "🌳": "درخت سبز", + "🌴": "نخل", + "🌵": "کاکتوس", + "🌿": "گیاه دارویی", + "🍁": "برگ افرا", + "🍇": "انگور", + "🍈": "طالبی", + "🍉": "هندوانه", + "🍊": "نارنگی", + "🍋": "لیمو ترش", + "🍌": "موز", + "🍍": "آناناس", + "🥭": "انبه", + "🍏": "سیب سبز", + "🍐": "گلابی", + "🍑": "هلو", + "🍒": "گیلاس", + "🍓": "توتفرنگی", + "🫐": "توت آبی", + "🥝": "کیوی", + "🍅": "گوجهفرنگی", + "🫒": "زیتون", + "🥥": "نارگیل", + "🥑": "آووکادو", + "🍆": "بادمجان", + "🥕": "هویج", + "🌽": "بلال ذرت", + "🌶️": "فلفل قرمز", + "🥬": "برگسبز", + "🥦": "بروکلی", + "🧅": "پیاز", + "🍄": "قارچ", + "🥜": "بادام زمینی", + "🥐": "کرواسان", + "🥖": "نان باگت", + "🥨": "چوبشور", + "🥯": "نان بیگل", + "🥞": "پنکیک", + "🧇": "وافل", + "🍔": "همبرگر", + "🍕": "پیتزا", + "🌭": "هات داگ", + "🌮": "تاکو", + "🍿": "ذرت بوداده", + "🦀": "خرچنگ", + "🦞": "لابستر", + "🍨": "بستنی", + "🍩": "دونات", + "🍪": "کلوچه", + "🎂": "کیک تولد", + "🧁": "کیک فنجانی", + "🍫": "تخته شکلات", + "🍬": "آبنبات", + "🍭": "آبنباتچوبی", + "🫖": "قوری", + "🧃": "پاکت نوشیدنی", + "🧉": "ماته", + "🧭": "قطبنما", + "🏔️": "قلهٔ برفی", + "🌋": "آتشفشان", + "🏕️": "چادرزنی", + "🏝️": "جزیرهٔ خالی از سکنه", + "🏡": "خانهٔ حیاطدار", + "⛲️": "فواره", + "🎠": "چرخوفلک اسبی", + "🎡": "چرخوفلک", + "🎢": "ترن هوایی", + "💈": "چراغ سلمانی", + "🚆": "قطار", + "🚋": "قطار خیابانی", + "🚍️": "اتوبوس روبهجلو", + "🚕": "تاکسی", + "🚗": "خودرو", + "🚚": "کامیون پخش", + "🚜": "تراکتور", + "🛵": "اسکوتر", + "🛺": "اتوریکشا", + "🛴": "چرخ پایی", + "🛹": "تختهٔ اسکیت", + "🛼": "کفش اسکیت", + "⚓️": "لنگر", + "⛵️": "قایق بادبانی", + "🛶": "بلم", + "🚤": "قایق تندرو", + "🚢": "کشتی", + "✈️": "هواپیما", + "🚁": "هلیکوپتر", + "🚠": "تلهکابین کوهستانی", + "🛰️": "ماهواره", + "🚀": "موشک", + "🛸": "بشقابپرنده", + "⏰": "ساعت رومیزی", + "🌙": "هلال ماه", + "🌡️": "دماسنج", + "☀️": "خورشید", + "🪐": "سیاره حلقهدار", + "🌟": "ستاره درخشان", + "🌀": "چرخند", + "🌈": "رنگین کمان", + "☂️": "چتر", + "❄️": "برفدانه", + "☄️": "ستاره دنبالهدار", + "🔥": "آتش", + "💧": "قطره", + "🌊": "موج آب", + "🎃": "فانوس کدو تنبل", + "✨": "درخشش", + "🎈": "بادکنک", + "🎉": "بمب کاغذ رنگی", + "🎏": "پرچمهای ماهی در باد", + "🎀": "روبان", + "🎁": "هدیهٔ کادوشده", + "🎟️": "بلیت ورود", + "🏆️": "جام ورزشی", + "⚽️": "توپ فوتبال", + "🏀": "بسکتبال", + "🏈": "فوتبال امریکایی", + "🎾": "تنیس", + "🥏": "فریزبی", + "🏓": "پینگپونگ", + "🏸": "بدمینتون", + "🤿": "ماسک غواصی", + "🥌": "سنگ کرلینگ", + "🎯": "پیکان در قلب هدف", + "🪀": "یویو", + "🪁": "بادبادک", + "🔮": "گوی", + "🎲": "تاس بازی", + "🧩": "قطعه پازل", + "🎨": "پالت نقاشی", + "🧵": "نخ", + "👕": "تیشرت", + "🧦": "جوراب", + "👗": "پیراهن زنانه", + "🩳": "شلوارک", + "🎒": "کولهپشتی", + "👟": "کفش دویدن", + "👑": "تاج", + "🧢": "کلاه لبهدار", + "💄": "رژلب", + "💍": "حلقه", + "💎": "جواهر", + "📢": "بلندگو", + "🎶": "نتهای موسیقی", + "🎙️": "میکروفون استودیویی", + "📻️": "رادیو", + "🎷": "ساکسیفون", + "🪗": "آکوردئون", + "🎸": "گیتار", + "🎺": "ترومپت", + "🎻": "ویولن", + "🪕": "بانجو", + "🥁": "طبل", + "☎️": "تلفن", + "🔋": "باتری", + "💿️": "سیدی", + "🧮": "چرتکه", + "🎬️": "کلاکت", + "💡": "لامپ", + "🔦": "چراغقوه", + "🏮": "فانوس کاغذی قرمز", + "📕": "کتاب بسته", + "🏷️": "برچسب", + "💳️": "کارت اعتباری", + "✏️": "مداد", + "🖌️": "قلممو", + "🖍️": "مدادشمعی", + "📌": "سنجاق", + "📎": "گیرهٔ کاغذ", + "🔑": "کلید", + "🪃": "بومرنگ", + "🏹": "تیر و کمان", + "⚖️": "ترازو", + "🧲": "آهنربا", + "🧪": "لولهٔ آزمایشگاه", + "🧬": "دیانای", + "🔬": "میکروسکوپ", + "🔭": "تلسکوپ", + "📡": "آنتن ماهواره", + "🪑": "صندلی", + "🧹": "جارو", + "🗿": "سردیس موآی" + }, + "fr": { + "👽️": "alien", + "🤖": "robot", + "🧠": "cerveau", + "👁️": "œil", + "🧙": "mage", + "🧚": "personnage féérique", + "🧜": "créature aquatique", + "🐵": "tête de singe", + "🦧": "orang-outan", + "🐶": "tête de chien", + "🐺": "loup", + "🦊": "renard", + "🦝": "raton laveur", + "🐱": "tête de chat", + "🦁": "tête de lion", + "🐯": "tête de tigre", + "🐴": "tête de cheval", + "🦄": "licorne", + "🦓": "zèbre", + "🦌": "cerf", + "🐮": "tête de vache", + "🐷": "tête de cochon", + "🐗": "sanglier", + "🐪": "dromadaire", + "🦙": "lama", + "🦒": "girafe", + "🐘": "éléphant", + "🦣": "mammouth", + "🦏": "rhinocéros", + "🐭": "tête de souris", + "🐰": "tête de lapin", + "🐿️": "écureuil", + "🦔": "hérisson", + "🦇": "chauve-souris", + "🐻": "ours", + "🐨": "koala", + "🦥": "paresseux", + "🦦": "loutre", + "🦘": "kangourou", + "🐥": "poussin de face", + "🐦️": "oiseau", + "🕊️": "colombe", + "🦆": "canard", + "🦉": "chouette", + "🦤": "dodo", + "🪶": "plume", + "🦩": "flamant", + "🦚": "paon", + "🦜": "perroquet", + "🐊": "crocodile", + "🐢": "tortue", + "🦎": "lézard", + "🐍": "serpent", + "🐲": "tête de dragon", + "🦕": "sauropode", + "🐳": "baleine soufflant par son évent", + "🐬": "dauphin", + "🦭": "phoque", + "🐟️": "poisson", + "🐠": "poisson tropical", + "🦈": "requin", + "🐙": "pieuvre", + "🐚": "coquille en spirale", + "🐌": "escargot", + "🦋": "papillon", + "🐛": "chenille", + "🐝": "abeille", + "🐞": "coccinelle", + "💐": "bouquet", + "🌹": "rose", + "🌺": "hibiscus", + "🌻": "tournesol", + "🌷": "tulipe", + "🌲": "conifère", + "🌳": "arbre à feuilles caduques", + "🌴": "palmier", + "🌵": "cactus", + "🌿": "feuille", + "🍁": "feuille d’érable", + "🍇": "raisin", + "🍈": "melon", + "🍉": "pastèque", + "🍊": "mandarine", + "🍋": "citron", + "🍌": "banane", + "🍍": "ananas", + "🥭": "mangue", + "🍏": "pomme verte", + "🍐": "poire", + "🍑": "pêche", + "🍒": "cerises", + "🍓": "fraise", + "🫐": "myrtilles", + "🥝": "kiwi", + "🍅": "tomate", + "🫒": "olive", + "🥥": "noix de coco", + "🥑": "avocat", + "🍆": "aubergine", + "🥕": "carotte", + "🌽": "épi de maïs", + "🌶️": "piment rouge", + "🥬": "légume à feuilles vertes", + "🥦": "brocoli", + "🧅": "oignon", + "🍄": "champignon", + "🥜": "cacahuètes", + "🥐": "croissant", + "🥖": "baguette", + "🥨": "bretzel", + "🥯": "bagel", + "🥞": "pancakes", + "🧇": "gaufre", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "pop-corn", + "🦀": "crabe", + "🦞": "homard", + "🍨": "glace", + "🍩": "doughnut", + "🍪": "cookie", + "🎂": "gâteau d’anniversaire", + "🧁": "cupcake", + "🍫": "barre chocolatée", + "🍬": "bonbon", + "🍭": "sucette", + "🫖": "théière", + "🧃": "briquette de jus", + "🧉": "maté", + "🧭": "boussole", + "🏔️": "montagne enneigée", + "🌋": "volcan", + "🏕️": "camping", + "🏝️": "île déserte", + "🏡": "maison avec jardin", + "⛲️": "fontaine", + "🎠": "cheval de manège", + "🎡": "grande roue", + "🎢": "montagnes russes", + "💈": "enseigne de barbier", + "🚆": "train", + "🚋": "wagon de tramway", + "🚍️": "bus de face", + "🚕": "taxi", + "🚗": "voiture", + "🚚": "camion de livraison", + "🚜": "tracteur", + "🛵": "scooter", + "🛺": "tuk tuk", + "🛴": "trottinette", + "🛹": "planche à roulettes", + "🛼": "patin à roulettes", + "⚓️": "ancre", + "⛵️": "voilier", + "🛶": "canoë", + "🚤": "hors-bord", + "🚢": "navire", + "✈️": "avion", + "🚁": "hélicoptère", + "🚠": "téléphérique", + "🛰️": "satellite", + "🚀": "fusée", + "🛸": "soucoupe volante", + "⏰": "réveil", + "🌙": "croissant de lune", + "🌡️": "thermomètre", + "☀️": "soleil", + "🪐": "planète à anneaux", + "🌟": "étoile brillante", + "🌀": "cyclone", + "🌈": "arc-en-ciel", + "☂️": "parapluie ouvert", + "❄️": "flocon", + "☄️": "comète", + "🔥": "feu", + "💧": "goutte d’eau", + "🌊": "vague", + "🎃": "citrouille", + "✨": "étincelles", + "🎈": "ballon gonflable", + "🎉": "cotillons", + "🎏": "koinobori", + "🎀": "ruban", + "🎁": "cadeau", + "🎟️": "billet d’entrée", + "🏆️": "trophée", + "⚽️": "ballon de football", + "🏀": "basket", + "🏈": "football américain", + "🎾": "tennis", + "🥏": "disque volant", + "🏓": "ping-pong", + "🏸": "badminton", + "🤿": "masque de plongée", + "🥌": "pierre de curling", + "🎯": "dans le mille", + "🪀": "yoyo", + "🪁": "cerf-volant", + "🔮": "boule de cristal", + "🎲": "dés", + "🧩": "pièce de puzzle", + "🎨": "palette de peinture", + "🧵": "bobine de fil", + "👕": "T-shirt", + "🧦": "chaussettes", + "👗": "robe", + "🩳": "short", + "🎒": "cartable", + "👟": "chaussure de sport", + "👑": "couronne", + "🧢": "casquette américaine", + "💄": "rouge à lèvres", + "💍": "bague", + "💎": "pierre précieuse", + "📢": "haut-parleur", + "🎶": "notes de musique", + "🎙️": "micro de studio", + "📻️": "radio", + "🎷": "saxophone", + "🪗": "accordéon", + "🎸": "guitare", + "🎺": "trompette", + "🎻": "violon", + "🪕": "banjo", + "🥁": "batterie", + "☎️": "téléphone", + "🔋": "pile", + "💿️": "CD", + "🧮": "abaque", + "🎬️": "clap", + "💡": "ampoule", + "🔦": "torche", + "🏮": "lampion rouge", + "📕": "livre fermé", + "🏷️": "étiquette", + "💳️": "carte bancaire", + "✏️": "crayon", + "🖌️": "pinceau", + "🖍️": "crayon pastel", + "📌": "punaise", + "📎": "trombone", + "🔑": "clé", + "🪃": "boomerang", + "🏹": "arc et flèche", + "⚖️": "balance à poids", + "🧲": "aimant", + "🧪": "tube à essai", + "🧬": "adn", + "🔬": "microscope", + "🔭": "télescope", + "📡": "antenne satellite", + "🪑": "chaise", + "🧹": "balai", + "🗿": "moai" + }, + "ga": { + "👽️": "eachtrán", + "🤖": "róbat", + "🧠": "inchinn", + "👁️": "súil", + "🧙": "duine draíochta", + "🧚": "síog", + "🧜": "duine murúch", + "🐵": "aghaidh moncaí", + "🦧": "órang-útan", + "🐶": "aghaidh madra", + "🐺": "aghaidh mic tíre", + "🦊": "aghaidh sionnaigh", + "🦝": "racún", + "🐱": "aghaidh cait", + "🦁": "aghaidh leoin", + "🐯": "aghaidh tíogair", + "🐴": "aghaidh capaill", + "🦄": "aghaidh aonbheannaigh", + "🦓": "séabra", + "🦌": "fia", + "🐮": "aghaidh bó", + "🐷": "aghaidh muice", + "🐗": "torc", + "🐪": "camall", + "🦙": "láma", + "🦒": "sioráf", + "🐘": "eilifint", + "🦣": "mamat", + "🦏": "srónbheannach", + "🐭": "aghaidh luiche", + "🐰": "aghaidh coinín", + "🐿️": "iora talún", + "🦔": "gráinneog", + "🦇": "ialtóg", + "🐻": "aghaidh béair", + "🐨": "cóála", + "🦥": "spadán", + "🦦": "madra uisce", + "🦘": "cangarú", + "🐥": "gearrshicín éadain", + "🐦️": "éan", + "🕊️": "colm", + "🦆": "lacha", + "🦉": "ulchabhán", + "🦤": "dódó", + "🪶": "cleite", + "🦩": "lasairéan", + "🦚": "péacóg", + "🦜": "pearóid", + "🐊": "crogall", + "🐢": "turtar", + "🦎": "earc luachra", + "🐍": "nathair", + "🐲": "aghaidh dragain", + "🦕": "sárapód", + "🐳": "míol mór ag séideadh", + "🐬": "deilf", + "🦭": "rón", + "🐟️": "iasc", + "🐠": "iasc teochreasa", + "🦈": "siorc", + "🐙": "ochtapas", + "🐚": "sliogán", + "🐌": "seilide", + "🦋": "féileacán", + "🐛": "míol", + "🐝": "beach mheala", + "🐞": "bóín", + "💐": "crobhaing bláthanna", + "🌹": "rós", + "🌺": "roiseog", + "🌻": "lus na gréine", + "🌷": "tiúilip", + "🌲": "crann síorghlas", + "🌳": "crann duillsilteach", + "🌴": "pailm", + "🌵": "cachtas", + "🌿": "luibh", + "🍁": "duilleog mhailpe", + "🍇": "caora fíniúna", + "🍈": "mealbhacán", + "🍉": "mealbhacán uisce", + "🍊": "táinséirín", + "🍋": "líomóid", + "🍌": "banana", + "🍍": "anann", + "🥭": "mangó", + "🍏": "úll glas", + "🍐": "piorra", + "🍑": "péitseog", + "🍒": "silíní", + "🍓": "sú talún", + "🫐": "fraochán", + "🥝": "cíobhaí", + "🍅": "tráta", + "🫒": "ológ", + "🥥": "cnó cócó", + "🥑": "abhacád", + "🍆": "ubhthoradh", + "🥕": "meacan dearg", + "🌽": "dias arbhair", + "🌶️": "píobar te", + "🥬": "glasra duilleach", + "🥦": "brocailí", + "🧅": "oiniún", + "🍄": "beacán", + "🥜": "pis talún", + "🥐": "croissant", + "🥖": "baguette", + "🥨": "preatsal", + "🥯": "béigeal", + "🥞": "pancóga", + "🧇": "vaifeal", + "🍔": "burgar", + "🍕": "píotsa", + "🌭": "brocaire te", + "🌮": "taco", + "🍿": "grán rósta", + "🦀": "portán", + "🦞": "gliomach", + "🍨": "uachtar reoite", + "🍩": "taoschnó", + "🍪": "briosca", + "🎂": "cáca breithe", + "🧁": "cístín cupa", + "🍫": "seacláid", + "🍬": "milseán", + "🍭": "líreacán", + "🫖": "taephota", + "🧃": "bosca dí", + "🧉": "maité", + "🧭": "compás", + "🏔️": "sliabh le sneachta", + "🌋": "bolcán", + "🏕️": "ag campáil", + "🏝️": "oileán díthreibhe", + "🏡": "teach le gairdín", + "⛲️": "scairdeán", + "🎠": "capall an roithleagáin ró", + "🎡": "roth Ferris", + "🎢": "rollchóstóir", + "💈": "cuaille bearbóra", + "🚆": "traein", + "🚋": "carráiste tram", + "🚍️": "bus atá ag teacht", + "🚕": "tacsaí", + "🚗": "gluaisteán", + "🚚": "trucail seachadta", + "🚜": "tarracóir", + "🛵": "scútar", + "🛺": "ricseá uathoibríoch", + "🛴": "scútar gan inneall", + "🛹": "clár scátála", + "🛼": "scáta rothach", + "⚓️": "ancaire", + "⛵️": "bád seoil", + "🛶": "canú", + "🚤": "luasbhád", + "🚢": "long", + "✈️": "eitleán", + "🚁": "héileacaptar", + "🚠": "cáblabhealach sléibhe", + "🛰️": "satailít", + "🚀": "roicéad", + "🛸": "sásar eitilte", + "⏰": "clog aláraim", + "🌙": "corrán gealaí", + "🌡️": "teirmiméadar", + "☀️": "grian", + "🪐": "pláinéad le fáinne", + "🌟": "réalta ag lonrú", + "🌀": "cioclón", + "🌈": "bogha báistí", + "☂️": "scáth báistí", + "❄️": "calóg shneachta", + "☄️": "cóiméad", + "🔥": "tine", + "💧": "braoinín", + "🌊": "tonn san uisce", + "🎃": "Seán na gealaí", + "✨": "drithlí", + "🎈": "balún", + "🎉": "pléiscín cóisire", + "🎏": "sraoilleán éisc", + "🎀": "ribín", + "🎁": "bronntanas", + "🎟️": "ticéid cead isteach", + "🏆️": "corn", + "⚽️": "liathróid sacair", + "🏀": "cispheil", + "🏈": "peil Mheiriceánach", + "🎾": "leadóg", + "🥏": "diosca eitilte", + "🏓": "ping pang", + "🏸": "badmantan", + "🤿": "masc tumadóireachta", + "🥌": "cloch churlála", + "🎯": "buille díreach", + "🪀": "yó-yó", + "🪁": "eitleog", + "🔮": "cruinneog chriostail", + "🎲": "dísle", + "🧩": "mír mearaí", + "🎨": "pailéad dathanna", + "🧵": "snáth", + "👕": "t-léine", + "🧦": "stocaí", + "👗": "gúna", + "🩳": "brístí gearra", + "🎒": "mála scoile", + "👟": "bróg reatha", + "👑": "coróin", + "🧢": "caipín speiceach", + "💄": "béaldath", + "💍": "fáinne", + "💎": "geamchloch", + "📢": "callaire", + "🎶": "nótaí ceoil", + "🎙️": "micreafón stiúideo", + "📻️": "raidió", + "🎷": "sacsafón", + "🪗": "bosca ceoil", + "🎸": "giotár", + "🎺": "trumpa", + "🎻": "veidhlín", + "🪕": "bainseó", + "🥁": "druma", + "☎️": "teileafón", + "🔋": "ceallra", + "💿️": "diosca optúil", + "🧮": "abacas", + "🎬️": "clabaire", + "💡": "bolgán solais", + "🔦": "tóirse", + "🏮": "laindéar dearg", + "📕": "leabhar dúnta", + "🏷️": "lipéad", + "💳️": "cárta creidmheasa", + "✏️": "peann luaidhe", + "🖌️": "scuab phéinteála", + "🖍️": "crián", + "📌": "tacóid ordóige", + "📎": "fáiscín páipéir", + "🔑": "eochair", + "🪃": "búmarang", + "🏹": "saighead is bogha", + "⚖️": "scála", + "🧲": "maighnéad", + "🧪": "triaileadán", + "🧬": "ADN", + "🔬": "micreascóp", + "🔭": "teileascóp", + "📡": "aeróg satailíte", + "🪑": "cathaoir", + "🧹": "scruab", + "🗿": "dealbh Oileán na Cásca" + }, + "he": { + "👽️": "חייזר", + "🤖": "רובוט", + "🧠": "מוח", + "👁️": "עין", + "🧙": "קוסם", + "🧚": "פיה", + "🧜": "בתולת ים", + "🐵": "פני קוף", + "🦧": "אורנגאוטן", + "🐶": "פני כלב", + "🐺": "זאב", + "🦊": "שועל", + "🦝": "רקון", + "🐱": "פני חתול", + "🦁": "אריה", + "🐯": "פני טיגריס", + "🐴": "פני סוס", + "🦄": "חד קרן", + "🦓": "זברה", + "🦌": "אייל", + "🐮": "פני פרה", + "🐷": "פני חזיר", + "🐗": "חזיר בר", + "🐪": "גמל", + "🦙": "למה", + "🦒": "ג׳ירף", + "🐘": "פיל", + "🦣": "ממותה", + "🦏": "קרנף", + "🐭": "פני עכבר", + "🐰": "פני ארנב", + "🐿️": "סנאי", + "🦔": "קיפוד", + "🦇": "עטלף", + "🐻": "דוב", + "🐨": "קואלה", + "🦥": "עצלן", + "🦦": "לוטרה", + "🦘": "קנגורו", + "🐥": "אפרוח מלפנים", + "🐦️": "ציפור", + "🕊️": "יונה", + "🦆": "ברווז", + "🦉": "ינשוף", + "🦤": "ציפור דודו", + "🪶": "נוצה", + "🦩": "פלמינגו", + "🦚": "טווס", + "🦜": "תוכי", + "🐊": "תנין", + "🐢": "צב", + "🦎": "לטאה", + "🐍": "נחש", + "🐲": "פני דרקון", + "🦕": "סרופוד", + "🐳": "לווייתן מתיז סילון מים", + "🐬": "דולפין", + "🦭": "כלב ים", + "🐟️": "דג", + "🐠": "דג טרופי", + "🦈": "כריש", + "🐙": "תמנון", + "🐚": "צדף מסולסל", + "🐌": "חלזון", + "🦋": "פרפר", + "🐛": "זחל", + "🐝": "דבורה", + "🐞": "פרת משה רבנו", + "💐": "זר פרחים", + "🌹": "ורד", + "🌺": "היביסקוס", + "🌻": "חמניה", + "🌷": "צבעוני", + "🌲": "עץ ירוק-עד", + "🌳": "עץ נשיר", + "🌴": "עץ דקל", + "🌵": "קקטוס", + "🌿": "צמח תבלין", + "🍁": "עלה מייפל", + "🍇": "ענבים", + "🍈": "מלון", + "🍉": "אבטיח", + "🍊": "קלמנטינה", + "🍋": "לימון", + "🍌": "בננה", + "🍍": "אננס", + "🥭": "מנגו", + "🍏": "תפוח ירוק", + "🍐": "אגס", + "🍑": "אפרסק", + "🍒": "דובדבנים", + "🍓": "תות שדה", + "🫐": "אוכמניות", + "🥝": "קיווי", + "🍅": "עגבנייה", + "🫒": "זית", + "🥥": "קוקוס", + "🥑": "אבוקדו", + "🍆": "חציל", + "🥕": "גזר", + "🌽": "קלח תירס", + "🌶️": "פלפל חריף", + "🥬": "עלים ירוקים", + "🥦": "ברוקולי", + "🧅": "בצל", + "🍄": "פטריה", + "🥜": "בוטנים", + "🥐": "קרואסון", + "🥖": "באגט", + "🥨": "בייגלה", + "🥯": "בייגל", + "🥞": "פנקייקס", + "🧇": "וופל", + "🍔": "המבורגר", + "🍕": "פיצה", + "🌭": "נקניקייה בלחמניה", + "🌮": "טאקו", + "🍿": "פופקורן", + "🦀": "סרטן", + "🦞": "לובסטר", + "🍨": "גלידה", + "🍩": "סופגניה", + "🍪": "עוגיה", + "🎂": "עוגת יום הולדת", + "🧁": "קפקייק", + "🍫": "חפיסת שוקולד", + "🍬": "סוכריה", + "🍭": "סוכריה על מקל", + "🫖": "קנקן תה", + "🧃": "קופסת מיץ", + "🧉": "מאטה", + "🧭": "מצפן", + "🏔️": "הר עם פסגה מושלגת", + "🌋": "הר געש", + "🏕️": "מחנאות", + "🏝️": "אי בודד", + "🏡": "בית עם גינה", + "⛲️": "מזרקה", + "🎠": "סוס בקרוסלה", + "🎡": "גלגל ענק", + "🎢": "רכבת הרים", + "💈": "עמוד מספרה", + "🚆": "רכבת", + "🚋": "קרון חשמלית", + "🚍️": "אוטובוס מלפנים", + "🚕": "מונית", + "🚗": "מכונית", + "🚚": "משאית", + "🚜": "טרקטור", + "🛵": "קטנוע", + "🛺": "ריקשה ממונעת", + "🛴": "קורקינט", + "🛹": "סקייטבורד", + "🛼": "גלגיליות", + "⚓️": "עוגן", + "⛵️": "סירת מפרש", + "🛶": "קאנו", + "🚤": "סירת מירוץ", + "🚢": "ספינה", + "✈️": "מטוס", + "🚁": "מסוק", + "🚠": "קרון רכבל", + "🛰️": "לוויין", + "🚀": "טיל", + "🛸": "צלחת מעופפת", + "⏰": "שעון מעורר", + "🌙": "סהר", + "🌡️": "מדחום", + "☀️": "שמש", + "🪐": "כוכב לכת עם טבעות", + "🌟": "כוכב זוהר", + "🌀": "הוריקן", + "🌈": "קשת בענן", + "☂️": "מטריה", + "❄️": "פתית שלג", + "☄️": "שביט", + "🔥": "אש", + "💧": "טיפה", + "🌊": "גל", + "🎃": "מנורת דלעת", + "✨": "נצנוץ", + "🎈": "בלון", + "🎉": "פצצת קונפטי", + "🎏": "דגלים בצורת דגי קוי", + "🎀": "סרט מתנה", + "🎁": "קופסת מתנה", + "🎟️": "כרטיס כניסה", + "🏆️": "גביע", + "⚽️": "כדורגל", + "🏀": "כדור סל", + "🏈": "פוטבול אמריקאי", + "🎾": "כדור טניס", + "🥏": "פריזבי", + "🏓": "פינג פונג", + "🏸": "בדמינטון", + "🤿": "מסיכת צלילה", + "🥌": "אבן קרלינג", + "🎯": "פגיעה בול", + "🪀": "יו-יו", + "🪁": "עפיפון", + "🔮": "כדור בדולח", + "🎲": "קוביית משחק", + "🧩": "חלק בפאזל", + "🎨": "פלטת צבעים", + "🧵": "חוט", + "👕": "חולצת טי", + "🧦": "גרביים", + "👗": "שמלה", + "🩳": "מכנסיים קצרים", + "🎒": "ילקוט", + "👟": "נעל ריצה", + "👑": "כתר", + "🧢": "כובע מצחייה", + "💄": "שפתון", + "💍": "טבעת", + "💎": "יהלום", + "📢": "מגפון", + "🎶": "תווים מוזיקליים", + "🎙️": "מיקרופון אולפן", + "📻️": "רדיו", + "🎷": "סקסופון", + "🪗": "אקורדיון", + "🎸": "גיטרה", + "🎺": "חצוצרה", + "🎻": "כינור", + "🪕": "בנג׳ו", + "🥁": "תוף", + "☎️": "טלפון רגיל", + "🔋": "סוללה", + "💿️": "דיסק אופטי", + "🧮": "חשבונייה", + "🎬️": "קלאפר", + "💡": "נורה", + "🔦": "פנס", + "🏮": "פנס נייר אדום", + "📕": "ספר סגור", + "🏷️": "תווית", + "💳️": "כרטיס אשראי", + "✏️": "עיפרון", + "🖌️": "מכחול", + "🖍️": "צבע פנדה", + "📌": "נעץ", + "📎": "אטב נייר", + "🔑": "מפתח", + "🪃": "בומרנג", + "🏹": "חץ וקשת", + "⚖️": "מאזניים", + "🧲": "מגנט", + "🧪": "מבחנה", + "🧬": "די אנ איי", + "🔬": "מיקרוסקופ", + "🔭": "טלסקופ", + "📡": "צלחת לוויין", + "🪑": "כיסא", + "🧹": "מטאטא", + "🗿": "פסל מאיי הפסחא" + }, + "hu": { + "👽️": "földönkívüli", + "🤖": "robot", + "🧠": "agy", + "👁️": "szem", + "🧙": "varázsló", + "🧚": "tündér", + "🧜": "sellő", + "🐵": "majomfej", + "🦧": "orangután", + "🐶": "kutyafej", + "🐺": "farkasfej", + "🦊": "rókafej", + "🦝": "mosómedve", + "🐱": "macskafej", + "🦁": "oroszlánfej", + "🐯": "tigrisfej", + "🐴": "lófej", + "🦄": "unikornisfej", + "🦓": "zebra", + "🦌": "szarvas", + "🐮": "tehénfej", + "🐷": "malacfej", + "🐗": "vaddisznó", + "🐪": "teve", + "🦙": "láma", + "🦒": "zsiráf", + "🐘": "elefánt", + "🦣": "mamut", + "🦏": "orrszarvú", + "🐭": "egérfej", + "🐰": "nyúlfej", + "🐿️": "mókus", + "🦔": "sün", + "🦇": "denevér", + "🐻": "medvefej", + "🐨": "koala", + "🦥": "lajhár", + "🦦": "vidra", + "🦘": "kenguru", + "🐥": "előre néző kiscsibe", + "🐦️": "madár", + "🕊️": "galamb", + "🦆": "kacsa", + "🦉": "bagoly", + "🦤": "dodó", + "🪶": "madártoll", + "🦩": "flamingó", + "🦚": "páva", + "🦜": "papagáj", + "🐊": "krokodil", + "🐢": "teknős", + "🦎": "gyík", + "🐍": "kígyó", + "🐲": "sárkányfej", + "🦕": "sauropoda", + "🐳": "fröcskölő bálna", + "🐬": "delfin", + "🦭": "fóka", + "🐟️": "hal", + "🐠": "trópusi hal", + "🦈": "cápa", + "🐙": "polip", + "🐚": "tengeri csiga", + "🐌": "csiga", + "🦋": "pillangó", + "🐛": "rovar", + "🐝": "méh", + "🐞": "katica", + "💐": "csokor", + "🌹": "rózsa", + "🌺": "hibiszkusz", + "🌻": "napraforgó", + "🌷": "tulipán", + "🌲": "örökzöld", + "🌳": "lombhullató fa", + "🌴": "pálmafa", + "🌵": "kaktusz", + "🌿": "gyógynövény", + "🍁": "juharlevél", + "🍇": "szőlő", + "🍈": "sárgadinnye", + "🍉": "görögdinnye", + "🍊": "mandarin", + "🍋": "citrom", + "🍌": "banán", + "🍍": "ananász", + "🥭": "mangó", + "🍏": "zöld alma", + "🍐": "körte", + "🍑": "őszibarack", + "🍒": "cseresznye", + "🍓": "eper", + "🫐": "áfonya", + "🥝": "kivi", + "🍅": "paradicsom", + "🫒": "olajbogyó", + "🥥": "kókuszdió", + "🥑": "avokádó", + "🍆": "padlizsán", + "🥕": "sárgarépa", + "🌽": "kukoricacső", + "🌶️": "erőspaprika", + "🥬": "leveles zöldség", + "🥦": "brokkoli", + "🧅": "hagyma", + "🍄": "gomba", + "🥜": "földimogyoró", + "🥐": "croissant", + "🥖": "bagett", + "🥨": "perec", + "🥯": "bagel", + "🥞": "palacsinta", + "🧇": "gofri", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "pattogatott kukorica", + "🦀": "rák", + "🦞": "homár", + "🍨": "fagylalt", + "🍩": "fánk", + "🍪": "sütemény", + "🎂": "születésnapi torta", + "🧁": "cupcake", + "🍫": "csokoládé", + "🍬": "cukorka", + "🍭": "nyalóka", + "🫖": "teáskanna", + "🧃": "italos doboz", + "🧉": "maté", + "🧭": "iránytű", + "🏔️": "hegy hótakaróval", + "🌋": "vulkán", + "🏕️": "sátorozás", + "🏝️": "lakatlan sziget", + "🏡": "ház kerttel", + "⛲️": "szökőkút", + "🎠": "ló a körhintáról", + "🎡": "óriáskerék", + "🎢": "hullámvasút", + "💈": "fodrászcégér", + "🚆": "vonat", + "🚋": "villamoskocsi", + "🚍️": "érkező busz", + "🚕": "taxi", + "🚗": "autó", + "🚚": "teherautó", + "🚜": "traktor", + "🛵": "robogó", + "🛺": "motoros riksa", + "🛴": "roller", + "🛹": "gördeszka", + "🛼": "görkorcsolya", + "⚓️": "vasmacska", + "⛵️": "vitorlás hajó", + "🛶": "kenu", + "🚤": "motoros siklócsónak", + "🚢": "hajó", + "✈️": "repülőgép", + "🚁": "helikopter", + "🚠": "hegyi felvonó", + "🛰️": "műhold", + "🚀": "rakéta", + "🛸": "repülő csészealj", + "⏰": "ébresztőóra", + "🌙": "növekvő hold", + "🌡️": "hőmérő", + "☀️": "nap", + "🪐": "gyűrűs bolygó", + "🌟": "fénylő csillag", + "🌀": "ciklon", + "🌈": "szivárvány", + "☂️": "esernyő", + "❄️": "hópehely", + "☄️": "üstökös", + "🔥": "tűz", + "💧": "csepp", + "🌊": "vízhullám", + "🎃": "töklámpás", + "✨": "szikrák", + "🎈": "léggömb", + "🎉": "partikellékek", + "🎏": "rizspapír pontyok", + "🎀": "szalag", + "🎁": "becsomagolt ajándék", + "🎟️": "belépőjegyek", + "🏆️": "kupa", + "⚽️": "foci", + "🏀": "kosárlabda", + "🏈": "amerikai foci", + "🎾": "tenisz", + "🥏": "frizbi", + "🏓": "pingpong", + "🏸": "tollaslabda", + "🤿": "búvármaszk", + "🥌": "curlingkő", + "🎯": "telitalálat", + "🪀": "jojó", + "🪁": "papírsárkány", + "🔮": "kristálygömb", + "🎲": "dobókocka", + "🧩": "kirakó", + "🎨": "festőpaletta", + "🧵": "cérna", + "👕": "póló", + "🧦": "zokni", + "👗": "ruha", + "🩳": "rövidnadrág", + "🎒": "iskolatáska", + "👟": "futócipő", + "👑": "korona", + "🧢": "sildes sapka", + "💄": "ajakrúzs", + "💍": "gyűrű", + "💎": "ékkő", + "📢": "hangosbeszélő", + "🎶": "hangjegyek", + "🎙️": "stúdiómikrofon", + "📻️": "rádió", + "🎷": "szaxofon", + "🪗": "harmonika", + "🎸": "gitár", + "🎺": "trombita", + "🎻": "hegedű", + "🪕": "bendzsó", + "🥁": "dob", + "☎️": "telefon", + "🔋": "elem", + "💿️": "optikai lemez", + "🧮": "abakusz", + "🎬️": "csapó", + "💡": "villanykörte", + "🔦": "zseblámpa", + "🏮": "piros papírlámpa", + "📕": "becsukott könyv", + "🏷️": "címke", + "💳️": "hitelkártya", + "✏️": "ceruza", + "🖌️": "ecset", + "🖍️": "zsírkréta", + "📌": "rajzszög", + "📎": "gemkapocs", + "🔑": "kulcs", + "🪃": "bumeráng", + "🏹": "íj és nyíl", + "⚖️": "kétkarú mérleg", + "🧲": "mágnes", + "🧪": "kémcső", + "🧬": "DNS", + "🔬": "mikroszkóp", + "🔭": "távcső", + "📡": "parabolaantenna", + "🪑": "szék", + "🧹": "seprű", + "🗿": "moai" + }, + "id": { + "👽️": "alien", + "🤖": "robot", + "🧠": "otak", + "👁️": "satu mata", + "🧙": "magi", + "🧚": "peri", + "🧜": "orang duyung", + "🐵": "wajah monyet", + "🦧": "orangutan", + "🐶": "wajah anjing", + "🐺": "serigala", + "🦊": "rubah", + "🦝": "rakun", + "🐱": "wajah kucing", + "🦁": "singa", + "🐯": "wajah harimau", + "🐴": "wajah kuda", + "🦄": "unicorn", + "🦓": "zebra", + "🦌": "rusa", + "🐮": "wajah sapi", + "🐷": "wajah babi", + "🐗": "babi hutan", + "🐪": "unta", + "🦙": "llama", + "🦒": "jerapah", + "🐘": "gajah", + "🦣": "mamut", + "🦏": "badak", + "🐭": "wajah tikus", + "🐰": "wajah kelinci", + "🐿️": "tupai", + "🦔": "landak", + "🦇": "kelelawar", + "🐻": "beruang", + "🐨": "koala", + "🦥": "kungkang", + "🦦": "berang-berang", + "🦘": "kanguru", + "🐥": "itik bayi menghadap ke depan", + "🐦️": "burung", + "🕊️": "merpati", + "🦆": "bebek", + "🦉": "burung hantu", + "🦤": "dodo", + "🪶": "bulu", + "🦩": "flamingo", + "🦚": "merak", + "🦜": "nuri", + "🐊": "buaya", + "🐢": "kura-kura", + "🦎": "kadal", + "🐍": "ular", + "🐲": "wajah naga", + "🦕": "sauropod", + "🐳": "ikan paus menyembur", + "🐬": "lumba-lumba", + "🦭": "anjing laut", + "🐟️": "ikan", + "🐠": "ikan tropis", + "🦈": "hiu", + "🐙": "gurita", + "🐚": "cangkang spiral", + "🐌": "siput", + "🦋": "kupu-kupu", + "🐛": "serangga kecil", + "🐝": "lebah madu", + "🐞": "kumbang betina", + "💐": "karangan bunga", + "🌹": "mawar", + "🌺": "kembang sepatu", + "🌻": "bunga matahari", + "🌷": "tulip", + "🌲": "pohon hijau abadi", + "🌳": "pohon meranggas", + "🌴": "pohon palem", + "🌵": "kaktus", + "🌿": "herba", + "🍁": "daun maple", + "🍇": "anggur", + "🍈": "melon", + "🍉": "semangka", + "🍊": "buah jeruk", + "🍋": "lemon", + "🍌": "pisang", + "🍍": "nanas", + "🥭": "mangga", + "🍏": "apel hijau", + "🍐": "pir", + "🍑": "persik", + "🍒": "ceri", + "🍓": "stroberi", + "🫐": "blueberry", + "🥝": "buah kiwi", + "🍅": "tomat", + "🫒": "zaitun", + "🥥": "kelapa", + "🥑": "alpukat", + "🍆": "terung", + "🥕": "wortel", + "🌽": "bonggol jagung", + "🌶️": "cabai pedas", + "🥬": "selada hijau", + "🥦": "brokoli", + "🧅": "bawang bombay", + "🍄": "jamur", + "🥜": "kacang tanah", + "🥐": "croissant", + "🥖": "roti baguette", + "🥨": "pretzel", + "🥯": "bagel", + "🥞": "panekuk", + "🧇": "wafel", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "kepiting", + "🦞": "lobster", + "🍨": "es krim", + "🍩": "donat", + "🍪": "biskuit", + "🎂": "kue ulang tahun", + "🧁": "kue mangkuk", + "🍫": "sebatang cokelat", + "🍬": "permen", + "🍭": "permen lolipop", + "🫖": "teko", + "🧃": "kotak minuman", + "🧉": "teh mate", + "🧭": "kompas", + "🏔️": "gunung dengan puncak bersalju", + "🌋": "gunung berapi", + "🏕️": "berkemah", + "🏝️": "pulau terpencil", + "🏡": "rumah dengan taman", + "⛲️": "air mancur", + "🎠": "komidi putar", + "🎡": "kincir ria", + "🎢": "roller coaster", + "💈": "tanda salon", + "🚆": "kereta", + "🚋": "gerbong trem", + "🚍️": "bus datang", + "🚕": "taksi", + "🚗": "mobil", + "🚚": "truk pengiriman", + "🚜": "traktor", + "🛵": "motor skuter", + "🛺": "bajaj", + "🛴": "otoped", + "🛹": "papan luncur", + "🛼": "sepatu roda", + "⚓️": "jangkar", + "⛵️": "perahu layar", + "🛶": "kano", + "🚤": "perahu cepat", + "🚢": "kapal", + "✈️": "pesawat", + "🚁": "helikopter", + "🚠": "kereta gantung", + "🛰️": "satelit", + "🚀": "roket", + "🛸": "piring terbang", + "⏰": "jam weker", + "🌙": "bulan sabit", + "🌡️": "termometer", + "☀️": "matahari", + "🪐": "planet bercincin", + "🌟": "bintang bersinar", + "🌀": "topan", + "🌈": "pelangi", + "☂️": "payung", + "❄️": "serpihan salju", + "☄️": "komet", + "🔥": "api", + "💧": "tetesan air", + "🌊": "ombak", + "🎃": "jack-o-lantern", + "✨": "berkilau", + "🎈": "balon", + "🎉": "party popper", + "🎏": "bendera ikan koi", + "🎀": "pita", + "🎁": "kado", + "🎟️": "tiket masuk", + "🏆️": "piala", + "⚽️": "bola sepak", + "🏀": "bola basket", + "🏈": "American football", + "🎾": "tenis", + "🥏": "lempar cakram", + "🏓": "ping pong", + "🏸": "bulu tangkis", + "🤿": "kacamata selam", + "🥌": "batu curling", + "🎯": "dart", + "🪀": "yo-yo", + "🪁": "layang-layang", + "🔮": "bola kristal", + "🎲": "dadu", + "🧩": "keping puzzle", + "🎨": "palet pelukis", + "🧵": "benang", + "👕": "kaos", + "🧦": "kaus kaki", + "👗": "gaun", + "🩳": "celana pendek", + "🎒": "tas sekolah", + "👟": "sepatu lari", + "👑": "mahkota", + "🧢": "topi berlidah", + "💄": "lipstik", + "💍": "cincin", + "💎": "batu permata", + "📢": "pengeras suara", + "🎶": "not-not musik", + "🎙️": "mikrofon studio", + "📻️": "radio", + "🎷": "saksofon", + "🪗": "akordeon", + "🎸": "gitar", + "🎺": "trompet", + "🎻": "biola", + "🪕": "banjo", + "🥁": "drum", + "☎️": "telepon", + "🔋": "baterai", + "💿️": "disk optik", + "🧮": "sempoa", + "🎬️": "papan sutradara", + "💡": "bohlam", + "🔦": "lampu senter", + "🏮": "lampion merah", + "📕": "buku tertutup", + "🏷️": "label", + "💳️": "kartu kredit", + "✏️": "pensil", + "🖌️": "kuas", + "🖍️": "krayon", + "📌": "paku penanda", + "📎": "klip kertas", + "🔑": "kunci", + "🪃": "bumerang", + "🏹": "busur dan panah", + "⚖️": "timbangan gantung", + "🧲": "magnet", + "🧪": "tabung uji", + "🧬": "dna", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "antena satelit", + "🪑": "bangku", + "🧹": "sapu", + "🗿": "moai" + }, + "is": { + "👽️": "geimvera", + "🤖": "vélmennisandlit", + "🧠": "heili", + "👁️": "auga", + "🧙": "galdramaður", + "🧚": "álfur", + "🧜": "sæbúi", + "🐵": "apahöfuð", + "🦧": "órangútan", + "🐶": "hundshöfuð", + "🐺": "úlfur", + "🦊": "refur", + "🦝": "þvottabjörn", + "🐱": "kattarhöfuð", + "🦁": "ljón", + "🐯": "tígrisdýrshöfuð", + "🐴": "hestshöfuð", + "🦄": "einhyrningur", + "🦓": "sebrahestur", + "🦌": "hjartardýr", + "🐮": "kýrhöfuð", + "🐷": "svínshöfuð", + "🐗": "villisvín", + "🐪": "drómedari", + "🦙": "lamadýr", + "🦒": "gíraffi", + "🐘": "fíll", + "🦣": "loðfíll", + "🦏": "nashyrningur", + "🐭": "músarhöfuð", + "🐰": "kanínuhöfuð", + "🐿️": "jarðíkorni", + "🦔": "broddgöltur", + "🦇": "leðurblaka", + "🐻": "björn", + "🐨": "kóalabjörn", + "🦥": "letidýr", + "🦦": "otur", + "🦘": "kengúra", + "🐥": "framhlið hænuunga", + "🐦️": "fugl", + "🕊️": "dúfa", + "🦆": "önd", + "🦉": "ugla", + "🦤": "dúdúfugl", + "🪶": "fjöður", + "🦩": "flamingói", + "🦚": "páfugl", + "🦜": "páfagaukur", + "🐊": "krókódíll", + "🐢": "skjaldbaka", + "🦎": "eðla", + "🐍": "snákur", + "🐲": "drekahöfuð", + "🦕": "graseðla", + "🐳": "blásandi hvalur", + "🐬": "höfrungur", + "🦭": "selur", + "🐟️": "fiskur", + "🐠": "hitabeltisfiskur", + "🦈": "hákarl", + "🐙": "kolkrabbi", + "🐚": "kuðungur", + "🐌": "snigill", + "🦋": "fiðrildi", + "🐛": "skordýr", + "🐝": "hunangsfluga", + "🐞": "maríuhæna", + "💐": "blómvöndur", + "🌹": "rós", + "🌺": "Havaírós", + "🌻": "sólblóm", + "🌷": "túlípani", + "🌲": "sígrænt tré", + "🌳": "sumargrænt tré", + "🌴": "pálmatré", + "🌵": "kaktus", + "🌿": "kryddjurt", + "🍁": "hlynslauf", + "🍇": "vínber", + "🍈": "melóna", + "🍉": "vatnsmelóna", + "🍊": "mandarína", + "🍋": "sítróna", + "🍌": "banani", + "🍍": "ananas", + "🥭": "mangó", + "🍏": "grænt epli", + "🍐": "pera", + "🍑": "ferskja", + "🍒": "kirsuber", + "🍓": "jarðarber", + "🫐": "bláber", + "🥝": "kíví", + "🍅": "tómatur", + "🫒": "ólífa", + "🥥": "kókoshneta", + "🥑": "avókadó", + "🍆": "eggaldin", + "🥕": "gulrót", + "🌽": "maís", + "🌶️": "chilipipar", + "🥬": "kál", + "🥦": "brokkólí", + "🧅": "laukur", + "🍄": "sveppur", + "🥜": "jarðhneta", + "🥐": "Croissant", + "🥖": "fransbrauð", + "🥨": "saltkringla", + "🥯": "beygla", + "🥞": "pönnukökur", + "🧇": "vaffla", + "🍔": "hamborgari", + "🍕": "pítsusneið", + "🌭": "pylsa í brauði", + "🌮": "takó", + "🍿": "popp", + "🦀": "krabbi", + "🦞": "humar", + "🍨": "kúluís", + "🍩": "kleinuhringur", + "🍪": "smákaka", + "🎂": "afmæliskaka", + "🧁": "bollakaka", + "🍫": "súkkulaði", + "🍬": "nammi", + "🍭": "sleikibrjóstsykur", + "🫖": "tekanna", + "🧃": "ferna", + "🧉": "mate", + "🧭": "áttaviti", + "🏔️": "snæviþakinn fjallstindur", + "🌋": "eldfjall", + "🏕️": "tjaldstæði", + "🏝️": "eyðieyja", + "🏡": "hús með garði", + "⛲️": "gosbrunnur", + "🎠": "hringekjuhestur", + "🎡": "parísarhjól", + "🎢": "rússíbani", + "💈": "rakarastofa", + "🚆": "lest", + "🚋": "sporvagn", + "🚍️": "strætó á móti", + "🚕": "leigubíll", + "🚗": "bíll", + "🚚": "flutningabíll", + "🚜": "dráttarvél", + "🛵": "vespa", + "🛺": "vélknúinn léttvagn", + "🛴": "hlaupahjól", + "🛹": "hjólabretti", + "🛼": "hjólaskautar", + "⚓️": "akkeri", + "⛵️": "seglskúta", + "🛶": "kanó", + "🚤": "hraðbátur", + "🚢": "skip", + "✈️": "flugvél", + "🚁": "þyrla", + "🚠": "kláfferja í fjalli", + "🛰️": "gervihnöttur", + "🚀": "eldflaug", + "🛸": "fljúgandi diskur", + "⏰": "vekjaraklukka", + "🌙": "hálfmáni", + "🌡️": "hitamælir", + "☀️": "sól", + "🪐": "pláneta með hringi", + "🌟": "skínandi stjarna", + "🌀": "fellibylur", + "🌈": "regnbogi", + "☂️": "regnhlíf", + "❄️": "snjókorn", + "☄️": "halastjarna", + "🔥": "eldur", + "💧": "dropi", + "🌊": "alda", + "🎃": "hrekkjavökugrasker", + "✨": "stjörnur", + "🎈": "blaðra", + "🎉": "knall", + "🎏": "fiskveifur", + "🎀": "borði", + "🎁": "gjöf", + "🎟️": "aðgöngumiðar", + "🏆️": "verðlaunabikar", + "⚽️": "fótbolti", + "🏀": "körfubolti", + "🏈": "amerískur fótbolti", + "🎾": "tennis", + "🥏": "frisbídiskur", + "🏓": "borðtennis", + "🏸": "badminton", + "🤿": "köfunargríma", + "🥌": "krullusteinn", + "🎯": "skotmark", + "🪀": "jójó", + "🪁": "flugdreki", + "🔮": "kristalskúla", + "🎲": "teningur", + "🧩": "púsl", + "🎨": "litapalletta", + "🧵": "tvinni", + "👕": "stuttermabolur", + "🧦": "sokkar", + "👗": "kjóll", + "🩳": "stuttbuxur", + "🎒": "skólataska", + "👟": "íþróttaskór", + "👑": "kóróna", + "🧢": "derhúfa", + "💄": "varalitur", + "💍": "hringur", + "💎": "gimsteinn", + "📢": "Almannavarnahátalari", + "🎶": "nótur", + "🎙️": "hljóðnemi í hljóðveri", + "📻️": "útvarp", + "🎷": "saxófónn", + "🪗": "harmonika", + "🎸": "gítar", + "🎺": "trompet", + "🎻": "fiðla", + "🪕": "banjó", + "🥁": "tromma", + "☎️": "sími", + "🔋": "rafhlaða", + "💿️": "blu-ray", + "🧮": "talnagrind", + "🎬️": "klapptré", + "💡": "ljósapera", + "🔦": "vasaljós", + "🏮": "rauður lampi", + "📕": "lokuð bók", + "🏷️": "merkimiði", + "💳️": "kreditkort", + "✏️": "blýantur", + "🖌️": "málningarpensill", + "🖍️": "vaxlitur", + "📌": "teiknibóla", + "📎": "bréfaklemma", + "🔑": "lykill", + "🪃": "búmerang", + "🏹": "bogi og ör", + "⚖️": "vog", + "🧲": "segull", + "🧪": "tilraunaglas", + "🧬": "dna", + "🔬": "smásjá", + "🔭": "sjónauki", + "📡": "gervihnattaloftnet", + "🪑": "stóll", + "🧹": "kústur", + "🗿": "moyai-stytta" + }, + "it": { + "👽️": "alieno", + "🤖": "faccina di robot", + "🧠": "cervello", + "👁️": "occhio", + "🧙": "mago", + "🧚": "fata", + "🧜": "sirena", + "🐵": "muso di scimmia", + "🦧": "orangotango", + "🐶": "muso di cane", + "🐺": "lupo", + "🦊": "volpe", + "🦝": "procione", + "🐱": "muso di gatto", + "🦁": "leone", + "🐯": "muso di tigre", + "🐴": "muso di cavallo", + "🦄": "unicorno", + "🦓": "zebra", + "🦌": "cervo", + "🐮": "muso di mucca", + "🐷": "muso di maiale", + "🐗": "cinghiale", + "🐪": "dromedario", + "🦙": "lama", + "🦒": "giraffa", + "🐘": "elefante", + "🦣": "mammut", + "🦏": "rinoceronte", + "🐭": "muso di topo", + "🐰": "muso di coniglio", + "🐿️": "scoiattolo", + "🦔": "riccio", + "🦇": "pipistrello", + "🐻": "orso", + "🐨": "koala", + "🦥": "bradipo", + "🦦": "lontra", + "🦘": "canguro", + "🐥": "pulcino visto di fronte", + "🐦️": "uccello", + "🕊️": "colomba", + "🦆": "anatra", + "🦉": "gufo", + "🦤": "dodo", + "🪶": "piuma", + "🦩": "fenicottero", + "🦚": "pavone", + "🦜": "pappagallo", + "🐊": "coccodrillo", + "🐢": "tartaruga", + "🦎": "lucertola", + "🐍": "serpente", + "🐲": "testa di drago", + "🦕": "sauropode", + "🐳": "balena che spruzza acqua", + "🐬": "delfino", + "🦭": "foca", + "🐟️": "pesce", + "🐠": "pesce tropicale", + "🦈": "squalo", + "🐙": "polpo", + "🐚": "conchiglia", + "🐌": "lumaca", + "🦋": "farfalla", + "🐛": "insetto", + "🐝": "ape", + "🐞": "coccinella", + "💐": "mazzo di fiori", + "🌹": "rosa", + "🌺": "ibisco", + "🌻": "girasole", + "🌷": "tulipano", + "🌲": "albero sempreverde", + "🌳": "albero deciduo", + "🌴": "palma", + "🌵": "cactus", + "🌿": "pianta", + "🍁": "foglia d’acero", + "🍇": "uva", + "🍈": "melone", + "🍉": "anguria", + "🍊": "mandarino", + "🍋": "limone", + "🍌": "banana", + "🍍": "ananas", + "🥭": "mango", + "🍏": "mela verde", + "🍐": "pera", + "🍑": "pesca", + "🍒": "ciliegie", + "🍓": "fragola", + "🫐": "mirtilli", + "🥝": "kiwi", + "🍅": "pomodoro", + "🫒": "oliva", + "🥥": "cocco", + "🥑": "avocado", + "🍆": "melanzana", + "🥕": "carota", + "🌽": "pannocchia", + "🌶️": "peperoncino", + "🥬": "verdure a foglia", + "🥦": "broccoli", + "🧅": "cipolla", + "🍄": "fungo", + "🥜": "arachidi", + "🥐": "croissant", + "🥖": "baguette", + "🥨": "pretzel", + "🥯": "bagel", + "🥞": "pancake", + "🧇": "waffle", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "granchio", + "🦞": "aragosta", + "🍨": "coppa di gelato", + "🍩": "ciambella", + "🍪": "biscotto", + "🎂": "torta di compleanno", + "🧁": "cupcake", + "🍫": "cioccolato", + "🍬": "caramella", + "🍭": "lecca lecca", + "🫖": "teiera", + "🧃": "bevanda monodose", + "🧉": "mate", + "🧭": "bussola", + "🏔️": "montagna innevata", + "🌋": "vulcano", + "🏕️": "campeggio", + "🏝️": "isola deserta", + "🏡": "casa con giardino", + "⛲️": "fontana", + "🎠": "cavallo da giostra", + "🎡": "ruota panoramica", + "🎢": "montagne russe", + "💈": "barbiere", + "🚆": "treno", + "🚋": "vagone del tram", + "🚍️": "bus in arrivo", + "🚕": "taxi", + "🚗": "auto", + "🚚": "camion", + "🚜": "trattore", + "🛵": "scooter", + "🛺": "risciò a motore", + "🛴": "monopattino", + "🛹": "skateboard", + "🛼": "pattini a rotelle", + "⚓️": "ancora", + "⛵️": "barca a vela", + "🛶": "canoa", + "🚤": "motoscafo", + "🚢": "nave", + "✈️": "aeroplano", + "🚁": "elicottero", + "🚠": "funivia", + "🛰️": "satellite", + "🚀": "razzo", + "🛸": "disco volante", + "⏰": "sveglia", + "🌙": "spicchio di luna", + "🌡️": "termometro", + "☀️": "sole", + "🪐": "pianeta con satellite", + "🌟": "stella che brilla", + "🌀": "ciclone", + "🌈": "arcobaleno", + "☂️": "ombrello", + "❄️": "fiocco di neve", + "☄️": "cometa", + "🔥": "fuoco", + "💧": "goccia", + "🌊": "onda", + "🎃": "zucca di Halloween", + "✨": "stelline", + "🎈": "palloncino", + "🎉": "spara coriandoli", + "🎏": "aquilone a forma di carpa", + "🎀": "fiocco", + "🎁": "regalo", + "🎟️": "biglietto d’ingresso", + "🏆️": "coppa", + "⚽️": "pallone da calcio", + "🏀": "palla da pallacanestro", + "🏈": "football americano", + "🎾": "tennis", + "🥏": "frisbee", + "🏓": "ping pong", + "🏸": "badminton", + "🤿": "maschera da sub", + "🥌": "stone da curling", + "🎯": "bersaglio", + "🪀": "yo-yo", + "🪁": "aquilone", + "🔮": "sfera di cristallo", + "🎲": "dado", + "🧩": "pezzo di puzzle", + "🎨": "tavolozza", + "🧵": "filo", + "👕": "t-shirt", + "🧦": "calzini", + "👗": "vestito", + "🩳": "pantaloncini", + "🎒": "zaino", + "👟": "scarpa sportiva", + "👑": "corona", + "🧢": "cappello con visiera", + "💄": "rossetto", + "💍": "anello", + "💎": "gemma", + "📢": "altoparlante", + "🎶": "note musicali", + "🎙️": "microfono radiofonico", + "📻️": "radio", + "🎷": "sassofono", + "🪗": "fisarmonica", + "🎸": "chitarra", + "🎺": "tromba", + "🎻": "violino", + "🪕": "banjo", + "🥁": "tamburo", + "☎️": "telefono fisso", + "🔋": "batteria", + "💿️": "disco ottico", + "🧮": "abaco", + "🎬️": "ciak", + "💡": "lampadina", + "🔦": "torcia", + "🏮": "lanterna rossa", + "📕": "libro chiuso", + "🏷️": "etichetta", + "💳️": "carta di credito", + "✏️": "matita", + "🖌️": "pennello", + "🖍️": "pastello a cera", + "📌": "puntina", + "📎": "graffetta", + "🔑": "chiave", + "🪃": "boomerang", + "🏹": "arco e freccia", + "⚖️": "bilancia a doppio piatto", + "🧲": "calamita", + "🧪": "provetta", + "🧬": "dna", + "🔬": "microscopio", + "🔭": "telescopio", + "📡": "antenna satellitare", + "🪑": "sedia", + "🧹": "scopa", + "🗿": "Moai" + }, + "ja": { + "👽️": "エイリアン", + "🤖": "ロボット", + "🧠": "脳", + "👁️": "片目", + "🧙": "魔法使い", + "🧚": "妖精", + "🧜": "人魚", + "🐵": "サルの顔", + "🦧": "オランウータン", + "🐶": "イヌの顔", + "🐺": "オオカミの顔", + "🦊": "キツネの顔", + "🦝": "アライグマ", + "🐱": "ネコの顔", + "🦁": "ライオンの顔", + "🐯": "トラの顔", + "🐴": "馬の顔", + "🦄": "ユニコーンの顔", + "🦓": "シマウマ", + "🦌": "シカ", + "🐮": "牛の顔", + "🐷": "ブタの顔", + "🐗": "イノシシ", + "🐪": "ラクダ", + "🦙": "ラマ", + "🦒": "キリン", + "🐘": "ゾウ", + "🦣": "マンモス", + "🦏": "サイ", + "🐭": "ネズミの顔", + "🐰": "ウサギの顔", + "🐿️": "リス", + "🦔": "ハリネズミ", + "🦇": "コウモリ", + "🐻": "クマの顔", + "🐨": "コアラ", + "🦥": "ナマケモノ", + "🦦": "カワウソ", + "🦘": "カンガルー", + "🐥": "前を向いているひよこ", + "🐦️": "鳥", + "🕊️": "ハト", + "🦆": "カモ", + "🦉": "フクロウ", + "🦤": "ドードー", + "🪶": "羽", + "🦩": "フラミンゴ", + "🦚": "クジャク", + "🦜": "オウム", + "🐊": "ワニ", + "🐢": "カメ", + "🦎": "トカゲ", + "🐍": "ヘビ", + "🐲": "ドラゴンの顔", + "🦕": "草食恐竜", + "🐳": "潮吹きクジラ", + "🐬": "イルカ", + "🦭": "アザラシ", + "🐟️": "魚", + "🐠": "熱帯魚", + "🦈": "サメ", + "🐙": "タコ", + "🐚": "巻き貝", + "🐌": "かたつむり", + "🦋": "チョウ", + "🐛": "毛虫", + "🐝": "ミツバチ", + "🐞": "テントウムシ", + "💐": "花束", + "🌹": "バラ", + "🌺": "ハイビスカス", + "🌻": "ヒマワリ", + "🌷": "チューリップ", + "🌲": "常緑樹", + "🌳": "落葉樹", + "🌴": "ヤシの木", + "🌵": "サボテン", + "🌿": "ハーブ", + "🍁": "かえで", + "🍇": "ぶどう", + "🍈": "メロン", + "🍉": "スイカ", + "🍊": "みかん", + "🍋": "レモン", + "🍌": "バナナ", + "🍍": "パイナップル", + "🥭": "マンゴー", + "🍏": "青リンゴ", + "🍐": "洋ナシ", + "🍑": "桃", + "🍒": "さくらんぼ", + "🍓": "いちご", + "🫐": "ブルーベリー", + "🥝": "キウイフルーツ", + "🍅": "トマト", + "🫒": "オリーブ", + "🥥": "ココナツ", + "🥑": "アボカド", + "🍆": "ナス", + "🥕": "人参", + "🌽": "とうもろこし", + "🌶️": "とうがらし", + "🥬": "葉野菜", + "🥦": "ブロッコリー", + "🧅": "タマネギ", + "🍄": "キノコ", + "🥜": "ピーナッツ", + "🥐": "クロワッサン", + "🥖": "バゲット", + "🥨": "プレッツェル", + "🥯": "ベーグル", + "🥞": "パンケーキ", + "🧇": "ワッフル", + "🍔": "ハンバーガー", + "🍕": "ピザ", + "🌭": "ホットドッグ", + "🌮": "タコス", + "🍿": "ポップコーン", + "🦀": "カニ", + "🦞": "ザリガニ", + "🍨": "アイスクリーム", + "🍩": "ドーナツ", + "🍪": "クッキー", + "🎂": "バースデーケーキ", + "🧁": "カップケーキ", + "🍫": "チョコレート", + "🍬": "キャンディ", + "🍭": "ぺろぺろキャンディ", + "🫖": "ティーポット", + "🧃": "紙パック飲料", + "🧉": "マテ茶", + "🧭": "コンパス", + "🏔️": "雪山", + "🌋": "火山", + "🏕️": "キャンプ", + "🏝️": "無人島", + "🏡": "庭付きの家", + "⛲️": "噴水", + "🎠": "メリーゴーランド", + "🎡": "観覧車", + "🎢": "ジェットコースター", + "💈": "床屋", + "🚆": "電車正面", + "🚋": "路面電車", + "🚍️": "バス正面", + "🚕": "タクシー", + "🚗": "自動車", + "🚚": "トラック", + "🚜": "トラクター", + "🛵": "スクーター", + "🛺": "三輪タクシー", + "🛴": "キックボード", + "🛹": "スケートボード", + "🛼": "ローラースケート", + "⚓️": "錨", + "⛵️": "ヨット", + "🛶": "カヌー", + "🚤": "スピードボート", + "🚢": "船", + "✈️": "飛行機", + "🚁": "ヘリコプター", + "🚠": "ケーブルカー", + "🛰️": "人工衛星", + "🚀": "ロケット", + "🛸": "空飛ぶ円盤", + "⏰": "目覚まし時計", + "🌙": "三日月", + "🌡️": "温度計", + "☀️": "太陽", + "🪐": "環のある惑星", + "🌟": "きらきら星", + "🌀": "渦巻き", + "🌈": "虹", + "☂️": "傘", + "❄️": "雪の結晶", + "☄️": "彗星", + "🔥": "火", + "💧": "水滴", + "🌊": "波", + "🎃": "ハロウィンかぼちゃ", + "✨": "きらきら", + "🎈": "風船", + "🎉": "クラッカー", + "🎏": "こいのぼり", + "🎀": "リボン", + "🎁": "プレゼント", + "🎟️": "入場券", + "🏆️": "トロフィー", + "⚽️": "サッカー", + "🏀": "バスケットボール", + "🏈": "アメフト", + "🎾": "テニス", + "🥏": "フリスビー", + "🏓": "卓球", + "🏸": "バドミントン", + "🤿": "ダイビング マスク", + "🥌": "カーリング", + "🎯": "的", + "🪀": "ヨーヨー", + "🪁": "たこ", + "🔮": "水晶玉", + "🎲": "サイコロ", + "🧩": "ジグソーパズル", + "🎨": "絵の具パレット", + "🧵": "糸", + "👕": "Tシャツ", + "🧦": "ソックス", + "👗": "ワンピース", + "🩳": "ショーツ", + "🎒": "バックパック", + "👟": "スニーカー", + "👑": "王冠", + "🧢": "キャップ", + "💄": "口紅", + "💍": "指輪", + "💎": "宝石", + "📢": "拡声器", + "🎶": "複数の音符", + "🎙️": "スタジオマイク", + "📻️": "ラジオ", + "🎷": "サックス", + "🪗": "アコーディオン", + "🎸": "ギター", + "🎺": "トランペット", + "🎻": "バイオリン", + "🪕": "バンジョー", + "🥁": "ドラム", + "☎️": "固定電話", + "🔋": "電池", + "💿️": "CD", + "🧮": "そろばん", + "🎬️": "カチンコ", + "💡": "電球", + "🔦": "懐中電灯", + "🏮": "赤ちょうちん", + "📕": "閉じた本", + "🏷️": "荷札", + "💳️": "クレジットカード", + "✏️": "鉛筆", + "🖌️": "絵筆", + "🖍️": "クレヨン", + "📌": "押しピン", + "📎": "クリップ", + "🔑": "鍵", + "🪃": "ブーメラン", + "🏹": "弓矢", + "⚖️": "天秤", + "🧲": "U字型磁石", + "🧪": "試験管", + "🧬": "DNA", + "🔬": "顕微鏡", + "🔭": "望遠鏡", + "📡": "パラボラアンテナ", + "🪑": "椅子", + "🧹": "ほうき", + "🗿": "モアイ" + }, + "ka": { + "👽️": "უცხოპლანეტელი", + "🤖": "რობოტის სახე", + "🧠": "ტვინი", + "👁️": "თვალი", + "🧙": "ჯადოქარი", + "🧚": "ფერია", + "🧜": "ადამიანთევზა", + "🐵": "მაიმუნის სახე", + "🦧": "ორანგუტანი", + "🐶": "ძაღლის სახე", + "🐺": "მგლის სახე", + "🦊": "მელიის თავი", + "🦝": "ენოტი", + "🐱": "კატის სახე", + "🦁": "ლომის სახე", + "🐯": "ვეფხვის სახე", + "🐴": "ცხენის სახე", + "🦄": "ზღაპრული მარტორქის სახე", + "🦓": "ზებრა", + "🦌": "ირემი", + "🐮": "ძროხის სახე", + "🐷": "ღორის სახე", + "🐗": "ტახი", + "🐪": "აქლემი", + "🦙": "ლამა", + "🦒": "ჟირაფი", + "🐘": "სპილო", + "🦣": "მამონტი", + "🦏": "მარტორქა", + "🐭": "თაგვის სახე", + "🐰": "კურდღლის სახე", + "🐿️": "ციყვი", + "🦔": "ზღარბი", + "🦇": "ღამურა", + "🐻": "დათვის სახე", + "🐨": "კოალა", + "🦥": "ზარმაცა", + "🦦": "წავი", + "🦘": "კენგურუ", + "🐥": "პატარა წიწილა წინიდან", + "🐦️": "ჩიტი", + "🕊️": "მტრედი", + "🦆": "იხვი", + "🦉": "ბუ", + "🦤": "დოდო", + "🪶": "ბუმბული", + "🦩": "ფლამინგო", + "🦚": "ფარშევანგი", + "🦜": "თუთიყუში", + "🐊": "ნიანგი", + "🐢": "კუ", + "🦎": "ხვლიკი", + "🐍": "გველი", + "🐲": "დრაკონის სახე", + "🦕": "ზაუროპოდი", + "🐳": "ვეშაპი ჭავლით", + "🐬": "დელფინი", + "🦭": "სელაპი", + "🐟️": "თევზი", + "🐠": "ტროპიკული თევზი", + "🦈": "ზვიგენი", + "🐙": "რვაფეხა", + "🐚": "სპირალური ნიჟარა", + "🐌": "ლოკოკინა", + "🦋": "პეპელა", + "🐛": "ბაღლინჯო", + "🐝": "ფუტკარი", + "🐞": "ჭიამაია", + "💐": "თაიგული", + "🌹": "ვარდი", + "🌺": "ჰიბისკუსი", + "🌻": "მზესუმზირა", + "🌷": "ტიტა", + "🌲": "მარადმწვანე", + "🌳": "ფოთლოვანი ხე", + "🌴": "პალმა", + "🌵": "კაქტუსი", + "🌿": "სამკურნალო მცენარე", + "🍁": "ნეკერჩხლის ფოთოლი", + "🍇": "ყურძენი", + "🍈": "ნესვი", + "🍉": "საზამთრო", + "🍊": "მანდარინი", + "🍋": "ლიმონი", + "🍌": "ბანანი", + "🍍": "ანანასი", + "🥭": "მანგო", + "🍏": "მწვანე ვაშლი", + "🍐": "მსხალი", + "🍑": "ატამი", + "🍒": "ალუბალი", + "🍓": "მარწყვი", + "🫐": "მოცვი", + "🥝": "კივი", + "🍅": "პამიდორი", + "🫒": "ზეითუნი", + "🥥": "ქოქოსი", + "🥑": "ავოკადო", + "🍆": "ბადრიჯანი", + "🥕": "სტაფილო", + "🌽": "სიმინდის ტარო", + "🌶️": "ცხარე წიწაკა", + "🥬": "სალათის ფოთოლი", + "🥦": "ბროკოლი", + "🧅": "ხახვი", + "🍄": "სოკო", + "🥜": "მიწის თხილი", + "🥐": "კრუასანი", + "🥖": "ბაგეტი", + "🥨": "პრეცელი", + "🥯": "ბეიგელი", + "🥞": "ბლინები", + "🧇": "ვაფლი", + "🍔": "ჰამბურგერი", + "🍕": "პიცა", + "🌭": "ჰოთდოგი", + "🌮": "ტაკო", + "🍿": "ბატი-ბუტი", + "🦀": "კიბორჩხალა", + "🦞": "კიბო", + "🍨": "ნაყინი", + "🍩": "დონატი", + "🍪": "ფხვიერი ნამცხვარი", + "🎂": "დაბადების დღის ტორტი", + "🧁": "ქაფქეიქი", + "🍫": "შოკოლადის ფილა", + "🍬": "კანფეტი", + "🍭": "კანფეტი ჯოხზე", + "🫖": "ჩაიდანი", + "🧃": "წვენი პაკეტში", + "🧉": "მატე", + "🧭": "კომპასი", + "🏔️": "თოვლით დაფარული მთა", + "🌋": "ვულკანი", + "🏕️": "კემპინგი", + "🏝️": "უკაცრიელი კუნძული", + "🏡": "სახლი ბაღით", + "⛲️": "შადრევანი", + "🎠": "კარუსელის ცხენი", + "🎡": "ეშმაკის ბორბალი", + "🎢": "ამერიკული გორაკები", + "💈": "დალაქის სვეტი", + "🚆": "მატარებელი", + "🚋": "ტრამვაის ვაგონი", + "🚍️": "მომავალი ავტობუსი", + "🚕": "ტაქსი", + "🚗": "ავტომობილი", + "🚚": "მიმწოდებელი სატვირთო მანქანა", + "🚜": "ტრაქტორი", + "🛵": "სკუტერი ძრავით", + "🛺": "ავტორიქშა", + "🛴": "ფეხის სკუტერი", + "🛹": "სკეიტბორდი", + "🛼": "გორგოლაჭიანი ციგურები", + "⚓️": "ღუზა", + "⛵️": "იალქნიანი ნავი", + "🛶": "კანოე", + "🚤": "გლისერი", + "🚢": "გემი", + "✈️": "თვითმფრინავი", + "🚁": "ვერტმფრენი", + "🚠": "მთის საბაგირო", + "🛰️": "თანამგზავრი", + "🚀": "რაკეტა", + "🛸": "მფრინავი თეფში", + "⏰": "მაღვიძარა", + "🌙": "ნამგალა მთვარე", + "🌡️": "თერმომეტრი", + "☀️": "მზე", + "🪐": "პლანეტა სარტყელით", + "🌟": "მბრწყინავი ვარსკვლავი", + "🌀": "ციკლონი", + "🌈": "ცისარტყელა", + "☂️": "ქოლგა", + "❄️": "თოვლის ფანტელი", + "☄️": "კომეტა", + "🔥": "ცეცხლი", + "💧": "წვეთი", + "🌊": "წყლის ტალღა", + "🎃": "ჰელოუინის გოგრა", + "✨": "ნაპერწკლები", + "🎈": "ბუშტი", + "🎉": "ტკაცუნა", + "🎏": "კობრებიანი ალამი", + "🎀": "ლენტი", + "🎁": "შეფუთული საჩუქარი", + "🎟️": "ბილეთები", + "🏆️": "პრიზი", + "⚽️": "ფეხბურთი", + "🏀": "კალათბურთი", + "🏈": "ამერიკული ფეხბურთი", + "🎾": "ჩოგბურთი", + "🥏": "მფრინავი დისკი", + "🏓": "მაგიდის ჩოგბურთი", + "🏸": "ბადმინტონი", + "🤿": "წყლის ნიღაბი", + "🥌": "კერლინგის ქვა", + "🎯": "პირდაპირი მოხვედრა", + "🪀": "იო-იო", + "🪁": "ფრანი", + "🔮": "კრისტალის ბურთი", + "🎲": "კამათელი", + "🧩": "პაზლი", + "🎨": "მხატვრის პალიტრა", + "🧵": "ძაფის კოჭა", + "👕": "მაისური", + "🧦": "წინდები", + "👗": "კაბა", + "🩳": "შორტები", + "🎒": "სკოლის ზურგჩანთა", + "👟": "ბოტასი", + "👑": "გვირგვინი", + "🧢": "ბეისბოლის კეპი", + "💄": "ტუჩის პომადა", + "💍": "ბეჭედი", + "💎": "ძვირფასი ქვა", + "📢": "რუპორი", + "🎶": "მუსიკალური ნოტები", + "🎙️": "სტუდიური მიკროფონი", + "📻️": "რადიო", + "🎷": "საქსოფონი", + "🪗": "აკორდეონი", + "🎸": "გიტარა", + "🎺": "საყვირი", + "🎻": "ვიოლინო", + "🪕": "ბანჯო", + "🥁": "ბარაბანი", + "☎️": "ტელეფონი", + "🔋": "ბატარეა", + "💿️": "ოპტიკური დისკი", + "🧮": "საანგარიშო დაფა", + "🎬️": "ნუმერატორი ტკაცუნათი", + "💡": "ნათურა", + "🔦": "ფანარი", + "🏮": "ქაღალდის წითელი ფარანი", + "📕": "დახურული წიგნი", + "🏷️": "იარლიყი", + "💳️": "საკრედიტო ბარათი", + "✏️": "ფანქარი", + "🖌️": "სახატავი ფუნჯი", + "🖍️": "ფერადი ფანქარი", + "📌": "ჭიკარტი", + "📎": "ქაღალდის სამაგრი", + "🔑": "გასაღები", + "🪃": "ბუმერანგი", + "🏹": "მშვილდ-ისარი", + "⚖️": "ბერკეტიანი სასწორი", + "🧲": "მაგნიტი", + "🧪": "სინჯარა", + "🧬": "დნმ", + "🔬": "მიკროსკოპი", + "🔭": "ტელესკოპი", + "📡": "თანამგზავრული ანტენა", + "🪑": "სკამი", + "🧹": "ცოცხი", + "🗿": "მოაი" + }, + "ko": { + "👽️": "외계인", + "🤖": "로봇 얼굴", + "🧠": "뇌", + "👁️": "눈", + "🧙": "마법사", + "🧚": "요정", + "🧜": "인어", + "🐵": "원숭이 얼굴", + "🦧": "오랑우탄", + "🐶": "강아지 얼굴", + "🐺": "늑대 얼굴", + "🦊": "여우 얼굴", + "🦝": "너구리", + "🐱": "고양이 얼굴", + "🦁": "사자 얼굴", + "🐯": "호랑이 얼굴", + "🐴": "말 얼굴", + "🦄": "유니콘 얼굴", + "🦓": "얼룩말", + "🦌": "사슴", + "🐮": "소 얼굴", + "🐷": "돼지 얼굴", + "🐗": "멧돼지", + "🐪": "낙타", + "🦙": "라마", + "🦒": "기린", + "🐘": "코끼리", + "🦣": "매머드", + "🦏": "코뿔소", + "🐭": "쥐 얼굴", + "🐰": "토끼 얼굴", + "🐿️": "얼룩다람쥐", + "🦔": "고슴도치", + "🦇": "박쥐", + "🐻": "곰 얼굴", + "🐨": "코알라", + "🦥": "나무늘보", + "🦦": "수달", + "🦘": "캥거루", + "🐥": "정면을 향해 날개를 편 병아리", + "🐦️": "새", + "🕊️": "비둘기", + "🦆": "오리", + "🦉": "부엉이", + "🦤": "도도새", + "🪶": "깃털", + "🦩": "홍학", + "🦚": "공작", + "🦜": "앵무새", + "🐊": "악어", + "🐢": "거북이", + "🦎": "도마뱀", + "🐍": "뱀", + "🐲": "용 얼굴", + "🦕": "초식 공룡", + "🐳": "물 뿜는 고래", + "🐬": "돌고래", + "🦭": "물개", + "🐟️": "물고기", + "🐠": "열대어", + "🦈": "상어", + "🐙": "문어", + "🐚": "달팽이집", + "🐌": "달팽이", + "🦋": "나비", + "🐛": "송충이", + "🐝": "꿀벌", + "🐞": "무당벌레", + "💐": "꽃다발", + "🌹": "장미꽃", + "🌺": "무궁화", + "🌻": "해바라기", + "🌷": "튤립", + "🌲": "소나무", + "🌳": "나무", + "🌴": "야자수", + "🌵": "선인장", + "🌿": "풀", + "🍁": "단풍잎", + "🍇": "포도", + "🍈": "멜론", + "🍉": "수박", + "🍊": "귤", + "🍋": "레몬", + "🍌": "바나나", + "🍍": "파인애플", + "🥭": "망고", + "🍏": "초록 사과", + "🍐": "배", + "🍑": "복숭아", + "🍒": "체리", + "🍓": "딸기", + "🫐": "블루베리", + "🥝": "키위", + "🍅": "토마토", + "🫒": "올리브", + "🥥": "코코넛", + "🥑": "아보카도", + "🍆": "가지", + "🥕": "당근", + "🌽": "옥수수", + "🌶️": "홍고추", + "🥬": "녹색 채소", + "🥦": "브로콜리", + "🧅": "양파", + "🍄": "버섯", + "🥜": "땅콩", + "🥐": "크루아상", + "🥖": "바게트", + "🥨": "프레첼", + "🥯": "베이글", + "🥞": "팬케이크", + "🧇": "와플", + "🍔": "햄버거", + "🍕": "피자", + "🌭": "핫도그", + "🌮": "타코", + "🍿": "팝콘", + "🦀": "꽃게", + "🦞": "바닷가재", + "🍨": "아이스크림", + "🍩": "도넛", + "🍪": "쿠키", + "🎂": "생일 케이크", + "🧁": "컵케이크", + "🍫": "초콜렛", + "🍬": "사탕", + "🍭": "롤리팝", + "🫖": "찻주전자", + "🧃": "음료 팩", + "🧉": "마테차", + "🧭": "나침반", + "🏔️": "눈 덮인 산", + "🌋": "화산", + "🏕️": "캠핑", + "🏝️": "사막 섬", + "🏡": "정원이 있는 집", + "⛲️": "분수", + "🎠": "회전 목마", + "🎡": "관람차", + "🎢": "롤러코스터", + "💈": "이발소", + "🚆": "기차", + "🚋": "트램 차량", + "🚍️": "오고 있는 버스", + "🚕": "택시", + "🚗": "자동차", + "🚚": "운송 트럭", + "🚜": "트랙터", + "🛵": "스쿠터", + "🛺": "경삼륜차", + "🛴": "킥보드", + "🛹": "스케이트보드", + "🛼": "롤러스케이트", + "⚓️": "닻", + "⛵️": "돛단배", + "🛶": "카누", + "🚤": "쾌속정", + "🚢": "선박", + "✈️": "비행기", + "🚁": "헬리콥터", + "🚠": "산악 케이블카", + "🛰️": "인공위성", + "🚀": "로켓", + "🛸": "비행접시", + "⏰": "알람 시계", + "🌙": "초승달", + "🌡️": "온도계", + "☀️": "태양", + "🪐": "고리가 있는 행성", + "🌟": "반짝이는 별", + "🌀": "태풍", + "🌈": "무지개", + "☂️": "우산", + "❄️": "눈송이", + "☄️": "혜성", + "🔥": "불", + "💧": "물방울", + "🌊": "파도", + "🎃": "할로윈", + "✨": "블링블링", + "🎈": "풍선", + "🎉": "파티", + "🎏": "물고기 깃발", + "🎀": "리본", + "🎁": "선물", + "🎟️": "입장 티켓", + "🏆️": "트로피", + "⚽️": "축구공", + "🏀": "농구", + "🏈": "미식축구공", + "🎾": "테니스", + "🥏": "원반", + "🏓": "탁구", + "🏸": "배드민턴", + "🤿": "다이빙 마스크", + "🥌": "컬링 스톤", + "🎯": "과녁 명중", + "🪀": "요요", + "🪁": "연", + "🔮": "수정 구슬", + "🎲": "주사위", + "🧩": "퍼즐", + "🎨": "팔레트", + "🧵": "실타래", + "👕": "티셔츠", + "🧦": "양말", + "👗": "원피스", + "🩳": "반바지", + "🎒": "학교 가방", + "👟": "운동화", + "👑": "왕관", + "🧢": "야구모자", + "💄": "립스틱", + "💍": "반지", + "💎": "원석", + "📢": "확성기", + "🎶": "노래 음표", + "🎙️": "스튜디오 마이크", + "📻️": "라디오", + "🎷": "색소폰", + "🪗": "아코디언", + "🎸": "기타", + "🎺": "트럼펫", + "🎻": "바이올린", + "🪕": "밴조", + "🥁": "드럼", + "☎️": "전화기", + "🔋": "배터리", + "💿️": "씨디", + "🧮": "주판", + "🎬️": "슬레이트", + "💡": "전구", + "🔦": "손전등", + "🏮": "일본식 등", + "📕": "펼치지 않은 책", + "🏷️": "라벨", + "💳️": "신용카드", + "✏️": "연필", + "🖌️": "붓", + "🖍️": "크레용", + "📌": "압정", + "📎": "클립", + "🔑": "열쇠", + "🪃": "부메랑", + "🏹": "활과 화살", + "⚖️": "접시저울", + "🧲": "자석", + "🧪": "시험관", + "🧬": "DNA", + "🔬": "현미경", + "🔭": "망원경", + "📡": "위성 안테나", + "🪑": "의자", + "🧹": "빗자루", + "🗿": "모아이" + }, + "lt": { + "👽️": "ateivis", + "🤖": "roboto veidas", + "🧠": "smegenys", + "👁️": "akis", + "🧙": "magas", + "🧚": "fėja", + "🧜": "undinius", + "🐵": "beždžionės snukutis", + "🦧": "orangutangas", + "🐶": "šuns snukutis", + "🐺": "vilko snukis", + "🦊": "lapės snukis", + "🦝": "meškėnas", + "🐱": "katės snukutis", + "🦁": "liūto snukis", + "🐯": "tigro snukis", + "🐴": "arklio snukis", + "🦄": "vienaragio snukis", + "🦓": "zebras", + "🦌": "elnias", + "🐮": "karvės snukis", + "🐷": "kiaulės snukis", + "🐗": "šernas", + "🐪": "kupranugaris", + "🦙": "lama", + "🦒": "žirafa", + "🐘": "dramblys", + "🦣": "mamutas", + "🦏": "raganosis", + "🐭": "pelės snukutis", + "🐰": "triušio snukutis", + "🐿️": "voverė", + "🦔": "ežys", + "🦇": "šikšnosparnis", + "🐻": "meškos snukis", + "🐨": "koala", + "🦥": "tinginys", + "🦦": "ūdra", + "🦘": "kengūra", + "🐥": "atsisukęs snapu viščiukas", + "🐦️": "paukštis", + "🕊️": "balandis", + "🦆": "antis", + "🦉": "pelėda", + "🦤": "drontas", + "🪶": "plunksna", + "🦩": "flamingas", + "🦚": "povas", + "🦜": "papūga", + "🐊": "krokodilas", + "🐢": "vėžlys", + "🦎": "driežas", + "🐍": "gyvatė", + "🐲": "drakono snukis", + "🦕": "sauropodas", + "🐳": "purškiantis banginis", + "🐬": "delfinas", + "🦭": "ruonis", + "🐟️": "žuvis", + "🐠": "tropinė žuvis", + "🦈": "ryklys", + "🐙": "aštuonkojis", + "🐚": "susisukusi kriauklė", + "🐌": "sraigė", + "🦋": "drugelis", + "🐛": "vabalas", + "🐝": "bitė", + "🐞": "boružė", + "💐": "puokštė", + "🌹": "rožė", + "🌺": "kinrožė", + "🌻": "saulėgrąža", + "🌷": "tulpė", + "🌲": "amžinai žaliuojantis medis", + "🌳": "lapuotis", + "🌴": "palmė", + "🌵": "kaktusas", + "🌿": "žolelė", + "🍁": "klevo lapas", + "🍇": "vynuogės", + "🍈": "melionas", + "🍉": "arbūzas", + "🍊": "mandarinas", + "🍋": "citrina", + "🍌": "bananas", + "🍍": "ananasas", + "🥭": "mangas", + "🍏": "žalias obuolys", + "🍐": "kriaušė", + "🍑": "persikas", + "🍒": "vyšnios", + "🍓": "braškė", + "🫐": "mėlynės", + "🥝": "kivis", + "🍅": "pomidoras", + "🫒": "alyvuogė", + "🥥": "kokosas", + "🥑": "avokadas", + "🍆": "baklažanas", + "🥕": "morka", + "🌽": "kukurūzo burbuolė", + "🌶️": "aštrusis pipiras", + "🥬": "lapinė daržovė", + "🥦": "brokolis", + "🧅": "svogūnas", + "🍄": "grybas", + "🥜": "žemės riešutas", + "🥐": "raguolis", + "🥖": "ilgas batonas", + "🥨": "riestainis", + "🥯": "apvalus riestainis", + "🥞": "blynai", + "🧇": "vaflis", + "🍔": "mėsainis", + "🍕": "pica", + "🌭": "dešrainis", + "🌮": "meksikietiškas paplotėlis", + "🍿": "spragėsiai", + "🦀": "krabas", + "🦞": "omaras", + "🍨": "ledai", + "🍩": "spurga", + "🍪": "sausainis", + "🎂": "gimtadienio tortas", + "🧁": "keksiukas", + "🍫": "šokolado batonėlis", + "🍬": "saldainis", + "🍭": "ledinukas ant pagaliuko", + "🫖": "arbatinukas", + "🧃": "gėrimo pakelis", + "🧉": "matė", + "🧭": "kompasas", + "🏔️": "kalnas su snieguota viršūne", + "🌋": "ugnikalnis", + "🏕️": "stovyklavimas", + "🏝️": "atoki sala", + "🏡": "namas su sodu", + "⛲️": "fontanas", + "🎠": "karuselės arkliukas", + "🎡": "apžvalgos ratas", + "🎢": "linksmieji kalneliai", + "💈": "kirpėjo suktukas", + "🚆": "traukinys", + "🚋": "tramvajaus vagonas", + "🚍️": "artėjantis autobusas", + "🚕": "taksi", + "🚗": "automobilis", + "🚚": "krovininis sunkvežimis", + "🚜": "traktorius", + "🛵": "motoroleris", + "🛺": "autorikša", + "🛴": "paspirtukas", + "🛹": "riedlentė", + "🛼": "riedučiai", + "⚓️": "inkaras", + "⛵️": "burlaivis", + "🛶": "kanoja", + "🚤": "motorinis kateris", + "🚢": "laivas", + "✈️": "lėktuvas", + "🚁": "sraigstasparnis", + "🚠": "kalnų keltuvas", + "🛰️": "palydovas", + "🚀": "raketa", + "🛸": "skraidanti lėkštė", + "⏰": "žadintuvas", + "🌙": "pusmėnulis", + "🌡️": "termometras", + "☀️": "saulė", + "🪐": "planeta su žiedu", + "🌟": "žėrinti žvaigždė", + "🌀": "ciklonas", + "🌈": "vaivorykštė", + "☂️": "skėtis", + "❄️": "snaigė", + "☄️": "kometa", + "🔥": "ugnis", + "💧": "lašas", + "🌊": "vandens banga", + "🎃": "žibintas iš moliūgo", + "✨": "kibirkštėlės", + "🎈": "balionas", + "🎉": "konfeti šaudyklė", + "🎏": "karpio vėtrungė", + "🎀": "kaspinas", + "🎁": "supakuota dovana", + "🎟️": "įėjimo bilietai", + "🏆️": "trofėjus", + "⚽️": "futbolo kamuolys", + "🏀": "krepšinio kamuolys", + "🏈": "amerikietiškojo futbolo kamuolys", + "🎾": "teniso kamuoliukas", + "🥏": "skraidantis diskas", + "🏓": "stalo tenisas", + "🏸": "badmintonas", + "🤿": "nardymo kaukė", + "🥌": "akmenslydis", + "🎯": "smūgis tiesiai į taikinį", + "🪀": "jo-jo", + "🪁": "aitvaras", + "🔮": "krištolinis rutulys", + "🎲": "kauliukas", + "🧩": "dėlionė", + "🎨": "dailininko paletė", + "🧵": "siūlas", + "👕": "marškinėliai", + "🧦": "kojinės", + "👗": "suknelė", + "🩳": "šortai", + "🎒": "mokyklinė kuprinė", + "👟": "bėgimo batelis", + "👑": "karūna", + "🧢": "reklaminė kepurė", + "💄": "lūpų dažai", + "💍": "žiedas", + "💎": "brangakmenis", + "📢": "garsiakalbio simbolis", + "🎶": "muzikos natos", + "🎙️": "studijos mikrofonas", + "📻️": "radijas", + "🎷": "saksofonas", + "🪗": "akordeonas", + "🎸": "gitara", + "🎺": "trimitas", + "🎻": "smuikas", + "🪕": "bandža", + "🥁": "būgnas", + "☎️": "telefonas", + "🔋": "baterija", + "💿️": "optinis diskas", + "🧮": "skaitytuvai", + "🎬️": "pliauškė", + "💡": "lemputė", + "🔦": "žibintuvėlis", + "🏮": "raudonas popierinis žibintas", + "📕": "užversta knyga", + "🏷️": "etiketė", + "💳️": "kredito kortelė", + "✏️": "pieštukas", + "🖌️": "teptukas", + "🖍️": "kreidelė", + "📌": "smeigtukas", + "📎": "sąvaržėlė", + "🔑": "raktas", + "🪃": "bumerangas", + "🏹": "lankas ir strėlė", + "⚖️": "svarstyklės su lėkštėmis", + "🧲": "magnetas", + "🧪": "mėgintuvėlis", + "🧬": "DNR", + "🔬": "mikroskopas", + "🔭": "teleskopas", + "📡": "palydovinė antena", + "🪑": "kėdė", + "🧹": "šluota", + "🗿": "moai" + }, + "mk": { + "👽️": "вонземјанин", + "🤖": "робот", + "🧠": "мозок", + "👁️": "око", + "🧙": "магичар", + "🧚": "самовила", + "🧜": "морско лице", + "🐵": "глава на мајмун", + "🦧": "орангутан", + "🐶": "глава на куче", + "🐺": "волк", + "🦊": "лисица", + "🦝": "ракун", + "🐱": "глава на мачка", + "🦁": "лав", + "🐯": "глава на тигар", + "🐴": "глава на коњ", + "🦄": "еднорог", + "🦓": "зебра", + "🦌": "елен", + "🐮": "глава на крава", + "🐷": "глава на прасе", + "🐗": "дива свиња", + "🐪": "камила", + "🦙": "лама", + "🦒": "жирафа", + "🐘": "слон", + "🦣": "мамут", + "🦏": "носорог", + "🐭": "глава на глушец", + "🐰": "глава на зајак", + "🐿️": "верверица", + "🦔": "еж", + "🦇": "лилјак", + "🐻": "мечка", + "🐨": "коала", + "🦥": "мрзливец", + "🦦": "видра", + "🦘": "кенгур", + "🐥": "пиле свртено нанапред", + "🐦️": "птица", + "🕊️": "гулабица", + "🦆": "патка", + "🦉": "був", + "🦤": "додо", + "🪶": "пердув", + "🦩": "фламинго", + "🦚": "паун", + "🦜": "папагал", + "🐊": "крокодил", + "🐢": "желка", + "🦎": "гуштер", + "🐍": "змија", + "🐲": "глава на змеј", + "🦕": "сауропод", + "🐳": "кит што прска вода", + "🐬": "делфин", + "🦭": "фока", + "🐟️": "риба", + "🐠": "тропска риба", + "🦈": "ајкула", + "🐙": "октопод", + "🐚": "спирална школка", + "🐌": "полжав", + "🦋": "пеперутка", + "🐛": "бубачка", + "🐝": "пчела", + "🐞": "бубамара", + "💐": "букет", + "🌹": "роза", + "🌺": "хибискус", + "🌻": "сончоглед", + "🌷": "лале", + "🌲": "зимзелено дрво", + "🌳": "листопадно дрво", + "🌴": "палма", + "🌵": "кактус", + "🌿": "билка", + "🍁": "јаворов лист", + "🍇": "грозје", + "🍈": "диња", + "🍉": "лубеница", + "🍊": "мандарина", + "🍋": "лимон", + "🍌": "банана", + "🍍": "ананас", + "🥭": "манго", + "🍏": "зелено јаболко", + "🍐": "круша", + "🍑": "праска", + "🍒": "цреши", + "🍓": "јагода", + "🫐": "боровинки", + "🥝": "киви", + "🍅": "домат", + "🫒": "маслинка", + "🥥": "кокосов орев", + "🥑": "авокадо", + "🍆": "модар патлиџан", + "🥕": "морков", + "🌽": "клас пченка", + "🌶️": "лута пиперка", + "🥬": "зелени листови", + "🥦": "брокола", + "🧅": "кромид", + "🍄": "печурка", + "🥜": "кикиритки", + "🥐": "кроасан", + "🥖": "багет", + "🥨": "перек", + "🥯": "ѓеврек", + "🥞": "палачинки", + "🧇": "вафла", + "🍔": "хамбургер", + "🍕": "пица", + "🌭": "хот дог", + "🌮": "тако", + "🍿": "пуканки", + "🦀": "рак", + "🦞": "јастог", + "🍨": "сладолед", + "🍩": "крофна", + "🍪": "колаче", + "🎂": "роденденска торта", + "🧁": "тортичка", + "🍫": "чоколадо", + "🍬": "бонбона", + "🍭": "лижалче", + "🫖": "чајник", + "🧃": "сокче во тетрапак", + "🧉": "мате", + "🧭": "компас", + "🏔️": "планина со снежен врв", + "🌋": "вулкан", + "🏕️": "кампување", + "🏝️": "пуст остров", + "🏡": "куќа со градина", + "⛲️": "фонтана", + "🎠": "коњче од вртелешка", + "🎡": "панорамско тркало", + "🎢": "тобоган", + "💈": "столб пред берберница", + "🚆": "воз", + "🚋": "вагон на трамвај", + "🚍️": "автобус што пристигнува", + "🚕": "такси", + "🚗": "автомобил", + "🚚": "камион за испорака", + "🚜": "трактор", + "🛵": "скутер", + "🛺": "авто-рикша", + "🛴": "тротинет", + "🛹": "скејтборд", + "🛼": "ролшуи", + "⚓️": "сидро", + "⛵️": "едреник", + "🛶": "кајак", + "🚤": "глисер", + "🚢": "брод", + "✈️": "авион", + "🚁": "хеликоптер", + "🚠": "планинска жичарница", + "🛰️": "сателит", + "🚀": "ракета", + "🛸": "летечка чинија", + "⏰": "часовник со аларм", + "🌙": "месечев срп", + "🌡️": "термометар", + "☀️": "сонце", + "🪐": "планета со прстени", + "🌟": "светлечка ѕвезда", + "🌀": "циклон", + "🌈": "виножито", + "☂️": "чадор", + "❄️": "снегулка", + "☄️": "комета", + "🔥": "оган", + "💧": "капка", + "🌊": "бран", + "🎃": "фенер од тиква", + "✨": "светки", + "🎈": "балон", + "🎉": "прскалка со конфети", + "🎏": "летало со крапови", + "🎀": "панделка", + "🎁": "завиткан подарок", + "🎟️": "влезни билети", + "🏆️": "трофеј", + "⚽️": "фудбалска топка", + "🏀": "баскет", + "🏈": "топка за американски фудбал", + "🎾": "тениско топче", + "🥏": "летечки диск", + "🏓": "пинг понг", + "🏸": "бадминтон", + "🤿": "нуркачка маска", + "🥌": "камен за карлинг", + "🎯": "пикадо", + "🪀": "јојо", + "🪁": "летечки змеј", + "🔮": "кристална топка", + "🎲": "коцка", + "🧩": "сложувалка", + "🎨": "сликарска палета", + "🧵": "конец", + "👕": "маица", + "🧦": "чорапи", + "👗": "фустан", + "🩳": "шорцеви", + "🎒": "училишен ранец", + "👟": "патика", + "👑": "круна", + "🧢": "капче", + "💄": "кармин", + "💍": "прстен", + "💎": "скапоцен камен", + "📢": "разглас", + "🎶": "музички ноти", + "🎙️": "студиски микрофон", + "📻️": "радио", + "🎷": "саксофон", + "🪗": "хармоника", + "🎸": "гитара", + "🎺": "труба", + "🎻": "виолина", + "🪕": "бенџо", + "🥁": "тапан", + "☎️": "телефон", + "🔋": "батерија", + "💿️": "цд", + "🧮": "сметалка", + "🎬️": "филмска клапа", + "💡": "светилка", + "🔦": "батериска ламба", + "🏮": "црвен фенер", + "📕": "затворена книга", + "🏷️": "етикета", + "💳️": "кредитна картичка", + "✏️": "молив", + "🖌️": "четка за боење", + "🖍️": "креон", + "📌": "притискач", + "📎": "спојувалка", + "🔑": "клуч", + "🪃": "бумеранг", + "🏹": "лак и стрела", + "⚖️": "механичка вага", + "🧲": "магнет", + "🧪": "епрувета", + "🧬": "днк", + "🔬": "микроскоп", + "🔭": "телескоп", + "📡": "сателитска антена", + "🪑": "стол", + "🧹": "метла", + "🗿": "монолитни статуи" + }, + "ms": { + "👽️": "makhluk asing", + "🤖": "robot", + "🧠": "otak", + "👁️": "mata", + "🧙": "ahli silap mata", + "🧚": "pari-pari", + "🧜": "manusia duyung", + "🐵": "muka monyet", + "🦧": "orang utan", + "🐶": "muka anjing", + "🐺": "serigala", + "🦊": "rubah", + "🦝": "rakun", + "🐱": "muka kucing", + "🦁": "singa", + "🐯": "muka harimau", + "🐴": "muka kuda", + "🦄": "unikorn", + "🦓": "zebra", + "🦌": "rusa", + "🐮": "muka lembu", + "🐷": "muka khinzir", + "🐗": "babi hutan", + "🐪": "unta", + "🦙": "llama", + "🦒": "zirafah", + "🐘": "gajah", + "🦣": "mamot", + "🦏": "badak sumbu", + "🐭": "muka tikus", + "🐰": "muka arnab", + "🐿️": "tupai tanah", + "🦔": "landak kecil", + "🦇": "kelawar", + "🐻": "beruang", + "🐨": "koala", + "🦥": "sloth", + "🦦": "memerang", + "🦘": "kanggaru", + "🐥": "anak ayam menghadap depan", + "🐦️": "burung", + "🕊️": "burung merpati", + "🦆": "itik", + "🦉": "burung hantu", + "🦤": "dodo", + "🪶": "bulu pelepah", + "🦩": "flamingo", + "🦚": "merak", + "🦜": "kakak tua", + "🐊": "buaya", + "🐢": "penyu", + "🦎": "cicak", + "🐍": "ular", + "🐲": "muka naga", + "🦕": "sauropod", + "🐳": "ikan paus memancutkan air", + "🐬": "ikan lumba-lumba", + "🦭": "anjing laut", + "🐟️": "ikan", + "🐠": "ikan tropika", + "🦈": "jerung", + "🐙": "sotong kurita", + "🐚": "kulit kerang berpilin", + "🐌": "siput", + "🦋": "rama-rama", + "🐛": "ulat", + "🐝": "lebah madu", + "🐞": "kumbang kura-kura", + "💐": "sejambak bunga", + "🌹": "bunga mawar", + "🌺": "bunga raya", + "🌻": "bunga matahari", + "🌷": "bunga tulip", + "🌲": "malar hijau", + "🌳": "pokok daun luruh", + "🌴": "pokok palma", + "🌵": "kaktus", + "🌿": "herba", + "🍁": "daun mapel", + "🍇": "anggur", + "🍈": "tembikai susu", + "🍉": "tembikai", + "🍊": "limau tangerin", + "🍋": "lemon", + "🍌": "pisang", + "🍍": "nanas", + "🥭": "mangga", + "🍏": "epal hijau", + "🍐": "buah pear", + "🍑": "buah pic", + "🍒": "buah ceri", + "🍓": "buah strawberi", + "🫐": "beri biru", + "🥝": "buah kiwi", + "🍅": "tomato", + "🫒": "buah zaitun", + "🥥": "kelapa", + "🥑": "avokado", + "🍆": "terung", + "🥕": "lobak merah", + "🌽": "tongkol jagung", + "🌶️": "cili api", + "🥬": "sayuran hijau", + "🥦": "brokoli", + "🧅": "bawang", + "🍄": "cendawan", + "🥜": "kacang tanah", + "🥐": "kroisan", + "🥖": "roti baguette", + "🥨": "pretzel", + "🥯": "bagel", + "🥞": "penkek", + "🧇": "wafel", + "🍔": "hamburger", + "🍕": "piza", + "🌭": "hot dog", + "🌮": "tako", + "🍿": "bertih jagung", + "🦀": "ketam", + "🦞": "udang karang", + "🍨": "ais krim", + "🍩": "donat", + "🍪": "biskut", + "🎂": "kek hari jadi", + "🧁": "kek cawan", + "🍫": "sekeping coklat", + "🍬": "gula-gula", + "🍭": "lolipop", + "🫖": "teko teh", + "🧃": "minuman air kotak", + "🧉": "mate", + "🧭": "kompas", + "🏔️": "gunung dengan puncak bersalji", + "🌋": "gunung berapi", + "🏕️": "berkhemah", + "🏝️": "pulau padang pasir", + "🏡": "rumah dengan taman", + "⛲️": "air pancut", + "🎠": "kuda karusel", + "🎡": "roda ferris", + "🎢": "roller coaster", + "💈": "jalur kedai gunting rambut", + "🚆": "kereta api", + "🚋": "gerabak trem", + "🚍️": "bas dari arah depan", + "🚕": "teksi", + "🚗": "kereta", + "🚚": "trak penghantaran", + "🚜": "traktor", + "🛵": "skuter bermotor", + "🛺": "lanca auto", + "🛴": "skuter tolak", + "🛹": "papan luncur", + "🛼": "kasut roda", + "⚓️": "sauh", + "⛵️": "perahu layar", + "🛶": "kanu", + "🚤": "bot laju", + "🚢": "kapal", + "✈️": "kapal terbang", + "🚁": "helikopter", + "🚠": "jalan kabel gunung", + "🛰️": "satelit", + "🚀": "roket", + "🛸": "piring terbang", + "⏰": "jam loceng", + "🌙": "bulan sabit", + "🌡️": "termometer", + "☀️": "matahari", + "🪐": "planet bergelang", + "🌟": "bintang bersinar", + "🌀": "taufan", + "🌈": "pelangi", + "☂️": "payung", + "❄️": "emping salji", + "☄️": "komet", + "🔥": "api", + "💧": "titisan air", + "🌊": "ombak laut", + "🎃": "tanglung labu", + "✨": "berkilauan", + "🎈": "belon", + "🎉": "pencetus bunyi parti", + "🎏": "panji-panji kap", + "🎀": "reben", + "🎁": "bungkusan hadiah", + "🎟️": "tiket masuk", + "🏆️": "piala", + "⚽️": "bola sepak", + "🏀": "bola keranjang", + "🏈": "bola sepak amerika", + "🎾": "tenis", + "🥏": "cakera terbang", + "🏓": "ping pong", + "🏸": "badminton", + "🤿": "topeng menyelam", + "🥌": "batu curling", + "🎯": "balingan tepat", + "🪀": "yo-yo", + "🪁": "layang-layang", + "🔮": "bola kristal", + "🎲": "dadu permainan", + "🧩": "kepingan suai gambar", + "🎨": "palet artis", + "🧵": "benang", + "👕": "kemeja-t", + "🧦": "stoking", + "👗": "gaun", + "🩳": "seluar pendek", + "🎒": "beg galas sekolah", + "👟": "kasut lari", + "👑": "mahkota", + "🧢": "kep berbil", + "💄": "gincu", + "💍": "cincin", + "💎": "batu permata", + "📢": "corong pembesar suara", + "🎶": "nota-nota muzik", + "🎙️": "mikrofon studio", + "📻️": "radio", + "🎷": "saksofon", + "🪗": "akordion", + "🎸": "gitar", + "🎺": "trompet", + "🎻": "biola", + "🪕": "banjo", + "🥁": "dram", + "☎️": "telefon", + "🔋": "bateri", + "💿️": "cakera optik", + "🧮": "sempua", + "🎬️": "papan pengetap", + "💡": "mentol lampu", + "🔦": "lampu suluh", + "🏮": "tanglung kertas merah", + "📕": "buku tertutup", + "🏷️": "label", + "💳️": "kad kredit", + "✏️": "pensel", + "🖌️": "berus cat", + "🖍️": "krayon", + "📌": "paku tekan", + "📎": "klip kertas", + "🔑": "kunci", + "🪃": "bumerang", + "🏹": "panah dan busur", + "⚖️": "neraca timbang", + "🧲": "magnet", + "🧪": "tabung uji", + "🧬": "dna", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "antena satelit", + "🪑": "kerusi", + "🧹": "penyapu", + "🗿": "moyai" + }, + "my": { + "👽️": "ဂြိုဟ်သား", + "🤖": "စက်ရုပ် မျက်နှာ", + "🧠": "ဦးနှောက်", + "👁️": "မျက်လုံး", + "🧙": "မှော်ဆရာ", + "🧚": "နတ်သမီး", + "🧜": "ရေလူ", + "🐵": "မျောက်ရုပ်", + "🦧": "အိုရန်အူတန်", + "🐶": "ခွေးရုပ်", + "🐺": "ဝံပုလွေရုပ်", + "🦊": "မြေခွေးရုပ်", + "🦝": "ရက်ကွန်းဝံ", + "🐱": "ကြောင်ရုပ်", + "🦁": "ခြင်္သေ့ရုပ်", + "🐯": "ကျားရုပ်", + "🐴": "မြင်းရုပ်", + "🦄": "ချိုတစ်ချောင်းမြင်းရုပ်", + "🦓": "မြင်းကျား", + "🦌": "သမင်", + "🐮": "နွားမရုပ်", + "🐷": "ဝက်ရုပ်", + "🐗": "တောဝက်", + "🐪": "ကုလားအုတ်", + "🦙": "လာမာ ကုလားအုတ်", + "🦒": "သစ်ကုလားအုတ်", + "🐘": "ဆင်", + "🦣": "ရှေးဆင်", + "🦏": "ကြံ့", + "🐭": "ကြွက် မျက်နှာ", + "🐰": "ယုန်ရုပ်", + "🐿️": "ကျောရိုးတွင် အဖြူအနက်စင်းပါ ရှဥ့်ငယ်တမျိုး", + "🦔": "ဖြူငယ်", + "🦇": "လင်းနို့", + "🐻": "ဝက်ဝံရုပ်", + "🐨": "ကိုအာလာ", + "🦥": "မျောက်မောင်းမ", + "🦦": "ဖျံ", + "🦘": "သားပိုက်ကောင်", + "🐥": "ရှေ့သို့မျက်နှာမူထားသည့် ကြက်ပေါက်စ", + "🐦️": "ငှက်", + "🕊️": "ချိုးငှက်", + "🦆": "ဘဲ", + "🦉": "ဇီးကွက်", + "🦤": "ဒိုဒို", + "🪶": "အတောင်", + "🦩": "ကြိုးကြာနီ", + "🦚": "ဒေါင်း", + "🦜": "ကြက်တူရွေး", + "🐊": "မိကျောင်း", + "🐢": "လိပ်", + "🦎": "အိမ်မြှောင်", + "🐍": "မြွေ", + "🐲": "နဂါးရုပ်", + "🦕": "ဆော်ရိုပေါ့ဒ်", + "🐳": "ရေမှုတ်ထုတ်နေသည့် ဝေလငါး", + "🐬": "လင်းပိုင်", + "🦭": "ပင်လယ်ဖျံ", + "🐟️": "ငါး", + "🐠": "အပူပိုင်း ငါး", + "🦈": "ငါးမန်း", + "🐙": "ဘဝဲ", + "🐚": "ခရုခွံ", + "🐌": "ခရု", + "🦋": "လိပ်ပြာ", + "🐛": "ပိုးကောင်ကလေး", + "🐝": "ပျား", + "🐞": "ပိုးတောင်မာ", + "💐": "ပန်းစည်း", + "🌹": "နှင်းဆီပန်း", + "🌺": "ခေါင်ရန်းပန်း", + "🌻": "နေကြာပန်း", + "🌷": "ကျူးလစ်ပန်း", + "🌲": "အမြဲစိမ်း သစ်ပင်", + "🌳": "ရွက်ပြတ်ပင်", + "🌴": "ထန်းပင်", + "🌵": "ရှားစောင်းပင်", + "🌿": "ဆေးဖက်ဝင်အပင်", + "🍁": "မေပယ်ရွက်", + "🍇": "စပျစ်သီး", + "🍈": "သခွားမွှေး", + "🍉": "ဖရဲသီး", + "🍊": "အခွံပွလိမ္မော်သီး", + "🍋": "ရှောက်သီး", + "🍌": "ငှက်ပျောသီး", + "🍍": "နာနတ်သီး", + "🥭": "သရက်သီး", + "🍏": "ပန်းသီးစိမ်း", + "🍐": "သစ်တော်သီး", + "🍑": "မက်မွန်သီး", + "🍒": "ချယ်ရီသီးများ", + "🍓": "စတော်ဘယ်ရီ", + "🫐": "ဘလူးဘယ်ရီ", + "🥝": "ကီဝီသီး", + "🍅": "ခရမ်းချဉ်သီး", + "🫒": "သံလွင်", + "🥥": "အုန်းသီး", + "🥑": "ထောပတ်သီး", + "🍆": "ခရမ်းသီး", + "🥕": "မုန်လာဥ", + "🌽": "ပြောင်းနှံ", + "🌶️": "ငရုတ်သီး", + "🥬": "အရွက်ဖားဖား သစ်ရွက်စိမ်း", + "🥦": "ပန်းဂေါ်ဖီစိမ်း", + "🧅": "ကြက်သွန်နီ", + "🍄": "မှို", + "🥜": "မြေပဲ", + "🥐": "လခြမ်းကွေးပေါင်မုန့်", + "🥖": "ပြင်သစ်ပေါင်မုန့်", + "🥨": "မုန့်ကြိုးလိမ်", + "🥯": "ပေါင်မုန့်အကွင်း", + "🥞": "မုန့်အိုးကင်းကြော်", + "🧇": "ဒေါင်းဖန်ဝါ မုန့်", + "🍔": "ဟမ်ဘာဂါ", + "🍕": "ပီဇာ", + "🌭": "ဟော့ဒေါ့", + "🌮": "တာကို", + "🍿": "ပြောင်းဖူးပေါက်ပေါက်", + "🦀": "ကဏန်း", + "🦞": "ကျောက်ပုစွန်", + "🍨": "ရေခဲမုန့်", + "🍩": "ဒိုးနတ်", + "🍪": "ကွတ်ကီး", + "🎂": "မွေးနေ့ ကိတ်မုန့်", + "🧁": "ကြက်ဥမုန့်", + "🍫": "ချောကလက် အချောင်း", + "🍬": "သကြားလုံး", + "🍭": "ချိုချဉ်ပလုတ်တုတ်", + "🫖": "ခရား", + "🧃": "အဖျော်ရေ ဘူး", + "🧉": "ချီမာရအို", + "🧭": "သံလိုက်အိမ်မြှောင်", + "🏔️": "ဆီးနှင်းဖုံးနေသည့် တောင်", + "🌋": "မီးတောင်", + "🏕️": "စခန်းချနေခြင်း", + "🏝️": "လူသူမဲ့ကျွန်း", + "🏡": "ပန်းခြံနှင့် အိမ်", + "⛲️": "အလှပြ ရေပန်း", + "🎠": "ချားရဟတ် မြင်း", + "🎡": "ချားရဟတ်", + "🎢": "သည်းဖိုရထား", + "💈": "ဆံပင်ညှပ်ဆိုင် တိုင်", + "🚆": "ရထား", + "🚋": "ဓာတ်ရထားတွဲ", + "🚍️": "လာနေသည့် ဘတ်စ်ကား", + "🚕": "တက်ကစီ", + "🚗": "မော်တော်ကား", + "🚚": "ပစ္စည်းပို့သည့် ထရပ်ကား", + "🚜": "လယ်ထွန်စက်", + "🛵": "မော်တာစက်တပ် စကူတာ", + "🛺": "သုံးဘီး", + "🛴": "စကူတာ", + "🛹": "စကိတ်လျှောပြား", + "🛼": "ရိုလာ စကိတ်", + "⚓️": "ကျောက်ဆူး", + "⛵️": "ရွက်လှေ", + "🛶": "ကနူး", + "🚤": "အမြန်စက်လှေ", + "🚢": "သင်္ဘော", + "✈️": "လေယာဉ်", + "🚁": "ရဟတ်ယာဉ်", + "🚠": "တောင်တက် ဓာတ်ကြိုးရထားလမ်း", + "🛰️": "ဂြိုဟ်တု", + "🚀": "ဒုံးယာဉ်", + "🛸": "ပန်းကန်ပြားပျံ", + "⏰": "နှိုးစက်နာရီ", + "🌙": "လခြမ်း", + "🌡️": "သာမိုမီတာ", + "☀️": "နေ", + "🪐": "ကွင်းပါရှိသည့် ဂြိုဟ်", + "🌟": "တောက်ပသည့် ကြယ်", + "🌀": "ဆိုင်ကလုန်း", + "🌈": "သက်တံ", + "☂️": "ထီး", + "❄️": "နှင်းပွင့်", + "☄️": "ကြယ်တံခွန်", + "🔥": "မီး", + "💧": "အစက်ကလေး", + "🌊": "ရေလှိုင်း", + "🎃": "ဖရုံသီး မီးအိမ်", + "✨": "မီးပွင့်များ", + "🎈": "လေပူဖောင်း", + "🎉": "ပါတီဗြောက်အိုး", + "🎏": "ငါးကြင်း တံခွန်", + "🎀": "ဖဲကြိုး", + "🎁": "ထုပ်ပိုးထားသည့် လက်ဆောင်", + "🎟️": "ဝင်ခွင့် လက်မှတ်များ", + "🏆️": "ဖလားဆု", + "⚽️": "ဘောလုံး", + "🏀": "ဘတ်စကက်ဘော", + "🏈": "အမေရိကန် ဘောလုံး", + "🎾": "တင်းနစ်", + "🥏": "ချပ်ပြားဝိုင်း ပစ်", + "🏓": "စားပွဲတင် တင်းနစ်", + "🏸": "ကြက်တောင်ရိုက်ခြင်း", + "🤿": "ရေငုပ် မျက်နှာဖုံး", + "🥌": "ရေခဲပြင်ကျောက်ပြားဝိုင်း", + "🎯": "ဗဟိုချက်ထိမှန်ခြင်း", + "🪀": "ယိုယို", + "🪁": "စွန်", + "🔮": "ကသိုဏ်းဖန်လုံး", + "🎲": "အန်စာတုံး", + "🧩": "ဂျစ်စော အပိုင်းအစ", + "🎨": "ဆေးစပ်ရာ အပြား", + "🧵": "အပ်ချည်", + "👕": "တီရှပ်", + "🧦": "ခြေအိတ်", + "👗": "ဝတ်စုံ", + "🩳": "ဘောင်းဘီတို", + "🎒": "ကျောင်းသား ကျောပိုးအိတ်", + "👟": "အပြေး ဖိနပ်", + "👑": "သရဖူ", + "🧢": "ကက်ဦးထုတ်", + "💄": "နှုတ်ခမ်းနီ", + "💍": "လက်စွပ်", + "💎": "ကျောက်မျက် ရတနာ", + "📢": "အသံချဲ့စက်", + "🎶": "ဂီတ သင်္ကေတများ", + "🎙️": "စတူဒီယို မိုက်ကရိုဖုန်း", + "📻️": "ရေဒီယို", + "🎷": "ဆက်ဆိုဖုန်း", + "🪗": "အကော်ဒီယံ", + "🎸": "ဂစ်တာ", + "🎺": "ခရာ", + "🎻": "တယော", + "🪕": "ဘင်ဂျို", + "🥁": "ဗုံ", + "☎️": "တယ်လီဖုန်း", + "🔋": "ဘက်ထရီ", + "💿️": "အလင်းသုံးဒစ်", + "🧮": "ပေသီးတွက်ခုံ", + "🎬️": "ရုပ်ရှင် ကလက်ဘုတ်", + "💡": "မီးလုံး", + "🔦": "ဓာတ်မီး", + "🏮": "စက္ကူမီးအိမ် အနီ", + "📕": "ပိတ်ထားသည့် စာအုပ်", + "🏷️": "တံဆိပ်", + "💳️": "ခရက်ဒစ်ကတ်", + "✏️": "ခဲတံ", + "🖌️": "စုတ်တံ", + "🖍️": "ရောင်စုံဖယောင်းခဲတံ", + "📌": "ဖိထိုးရသည့် ပင်အပ်", + "📎": "စက္ကူညှပ်", + "🔑": "သော့", + "🪃": "ထိကပြန်", + "🏹": "လေးနှင့် မြား", + "⚖️": "ချိန်ခွင်", + "🧲": "သံလိုက်", + "🧪": "ဖန်ပြွန်", + "🧬": "မျိုးရိုးဗီဇ", + "🔬": "အဏုကြည့်မှန်ပြောင်း", + "🔭": "အဝေးကြည့်မှန်ပြောင်း", + "📡": "ဂြိုဟ်တု စလောင်း", + "🪑": "ကုလားထိုင်", + "🧹": "တံမြက်စည်း", + "🗿": "မိုအိုင်" + }, + "nl": { + "👽️": "buitenaards wezen", + "🤖": "robot", + "🧠": "brein", + "👁️": "oog", + "🧙": "magiër", + "🧚": "elfje", + "🧜": "zeemeermin", + "🐵": "apengezicht", + "🦧": "orang-oetang", + "🐶": "hondengezicht", + "🐺": "wolf", + "🦊": "vos", + "🦝": "wasbeer", + "🐱": "kattengezicht", + "🦁": "leeuw", + "🐯": "tijgergezicht", + "🐴": "paardengezicht", + "🦄": "eenhoorn", + "🦓": "zebra", + "🦌": "hert", + "🐮": "koeiengezicht", + "🐷": "varkensgezicht", + "🐗": "zwijn", + "🐪": "dromedaris", + "🦙": "lama", + "🦒": "giraffe", + "🐘": "olifant", + "🦣": "mammoet", + "🦏": "neushoorn", + "🐭": "muizengezicht", + "🐰": "konijnengezicht", + "🐿️": "eekhoorn", + "🦔": "egel", + "🦇": "vleermuis", + "🐻": "beer", + "🐨": "koala", + "🦥": "luiaard", + "🦦": "otter", + "🦘": "kangoeroe", + "🐥": "naar voren kijkend kuikentje", + "🐦️": "vogel", + "🕊️": "duif", + "🦆": "eend", + "🦉": "uil", + "🦤": "dodo", + "🪶": "veer", + "🦩": "flamingo", + "🦚": "pauw", + "🦜": "papegaai", + "🐊": "krokodil", + "🐢": "schildpad", + "🦎": "hagedis", + "🐍": "slang", + "🐲": "drakengezicht", + "🦕": "sauropode", + "🐳": "spuitende walvis", + "🐬": "dolfijn", + "🦭": "zeehond", + "🐟️": "vis", + "🐠": "tropische vis", + "🦈": "haai", + "🐙": "octopus", + "🐚": "schelp", + "🐌": "slak", + "🦋": "vlinder", + "🐛": "insect", + "🐝": "honingbij", + "🐞": "lieveheersbeestje", + "💐": "boeket", + "🌹": "roos", + "🌺": "hibiscus", + "🌻": "zonnebloem", + "🌷": "tulp", + "🌲": "naaldboom", + "🌳": "loofboom", + "🌴": "palmboom", + "🌵": "cactus", + "🌿": "kruid", + "🍁": "esdoornblad", + "🍇": "druiven", + "🍈": "meloen", + "🍉": "watermeloen", + "🍊": "mandarijn", + "🍋": "citroen", + "🍌": "banaan", + "🍍": "ananas", + "🥭": "mango", + "🍏": "groene appel", + "🍐": "peer", + "🍑": "perzik", + "🍒": "kersen", + "🍓": "aardbei", + "🫐": "bosbessen", + "🥝": "kiwi", + "🍅": "tomaat", + "🫒": "olijf", + "🥥": "kokosnoot", + "🥑": "avocado", + "🍆": "aubergine", + "🥕": "wortel", + "🌽": "maïskolf", + "🌶️": "hete peper", + "🥬": "bladgroente", + "🥦": "broccoli", + "🧅": "ui", + "🍄": "paddenstoel", + "🥜": "pinda’s", + "🥐": "croissant", + "🥖": "stokbrood", + "🥨": "pretzel", + "🥯": "bagel", + "🥞": "pannenkoeken", + "🧇": "wafel", + "🍔": "hamburger", + "🍕": "pizzapunt", + "🌭": "hotdog", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "krab", + "🦞": "kreeft", + "🍨": "ijs", + "🍩": "donut", + "🍪": "koekje", + "🎂": "verjaardagstaart", + "🧁": "cupcake", + "🍫": "chocoladereep", + "🍬": "snoep", + "🍭": "lolly", + "🫖": "theepot", + "🧃": "pakje drinken", + "🧉": "maté", + "🧭": "kompas", + "🏔️": "berg met besneeuwde toppen", + "🌋": "vulkaan", + "🏕️": "kamperen", + "🏝️": "onbewoond eiland", + "🏡": "huis met tuin", + "⛲️": "fontein", + "🎠": "draaimolenpaard", + "🎡": "reuzenrad", + "🎢": "achtbaan", + "💈": "kapperspaal", + "🚆": "trein", + "🚋": "tramwagon", + "🚍️": "naderende bus", + "🚕": "taxi", + "🚗": "auto", + "🚚": "bestelbus", + "🚜": "tractor", + "🛵": "scooter", + "🛺": "riksja", + "🛴": "step", + "🛹": "skateboard", + "🛼": "rolschaats", + "⚓️": "anker", + "⛵️": "zeilboot", + "🛶": "kano", + "🚤": "speedboot", + "🚢": "schip", + "✈️": "vliegtuig", + "🚁": "helikopter", + "🚠": "bergkabelbaan", + "🛰️": "satelliet", + "🚀": "raket", + "🛸": "vliegende schotel", + "⏰": "wekker", + "🌙": "maansikkel", + "🌡️": "thermometer", + "☀️": "zon", + "🪐": "planeet met ringen", + "🌟": "stralende ster", + "🌀": "cycloon", + "🌈": "regenboog", + "☂️": "paraplu", + "❄️": "sneeuwvlok", + "☄️": "komeet", + "🔥": "vuur", + "💧": "druppel", + "🌊": "golf", + "🎃": "halloweenlantaarn", + "✨": "sterretjes", + "🎈": "ballon", + "🎉": "feestknaller", + "🎏": "karperslinger", + "🎀": "strik", + "🎁": "ingepakt cadeau", + "🎟️": "entreekaartjes", + "🏆️": "trofee", + "⚽️": "voetbal", + "🏀": "basketbal", + "🏈": "american football", + "🎾": "tennis", + "🥏": "frisbee", + "🏓": "pingpong", + "🏸": "badminton", + "🤿": "duikbril", + "🥌": "curlingsteen", + "🎯": "directe treffer", + "🪀": "jojo", + "🪁": "vlieger", + "🔮": "kristallen bol", + "🎲": "dobbelsteen", + "🧩": "puzzelstukje", + "🎨": "schilderspalet", + "🧵": "garen", + "👕": "T-shirt", + "🧦": "sokken", + "👗": "jurk", + "🩳": "korte broek", + "🎒": "schooltas", + "👟": "sportschoen", + "👑": "kroon", + "🧢": "pet", + "💄": "lippenstift", + "💍": "ring", + "💎": "edelsteen", + "📢": "luidspreker", + "🎶": "muzieknoten", + "🎙️": "studiomicrofoon", + "📻️": "radio", + "🎷": "saxofoon", + "🪗": "accordeon", + "🎸": "gitaar", + "🎺": "trompet", + "🎻": "viool", + "🪕": "banjo", + "🥁": "trommel", + "☎️": "telefoon", + "🔋": "batterij", + "💿️": "optische schijf", + "🧮": "abacus", + "🎬️": "klapbord", + "💡": "gloeilamp", + "🔦": "zaklamp", + "🏮": "rode papieren lantaarn", + "📕": "gesloten boek", + "🏷️": "label", + "💳️": "creditcard", + "✏️": "potlood", + "🖌️": "kwast", + "🖍️": "krijtje", + "📌": "punaise", + "📎": "paperclip", + "🔑": "sleutel", + "🪃": "boemerang", + "🏹": "pijl en boog", + "⚖️": "weegschaal", + "🧲": "magneet", + "🧪": "reageerbuis", + "🧬": "DNA", + "🔬": "microscoop", + "🔭": "telescoop", + "📡": "satellietschotel", + "🪑": "hoge stoel", + "🧹": "bezem", + "🗿": "moai-beeld" + }, + "pl": { + "👽️": "ufoludek", + "🤖": "głowa robota", + "🧠": "mózg", + "👁️": "oko", + "🧙": "mag", + "🧚": "wróżka", + "🧜": "syrena", + "🐵": "głowa małpy", + "🦧": "orangutan", + "🐶": "głowa psa", + "🐺": "głowa wilka", + "🦊": "głowa lisa", + "🦝": "szop", + "🐱": "głowa kota", + "🦁": "głowa lwa", + "🐯": "głowa tygrysa", + "🐴": "głowa konia", + "🦄": "głowa jednorożca", + "🦓": "zebra", + "🦌": "jeleń", + "🐮": "głowa krowy", + "🐷": "głowa świni", + "🐗": "dzik", + "🐪": "wielbłąd", + "🦙": "lama", + "🦒": "żyrafa", + "🐘": "słoń", + "🦣": "mamut", + "🦏": "nosorożec", + "🐭": "głowa myszy", + "🐰": "głowa królika", + "🐿️": "pręgowiec", + "🦔": "jeż", + "🦇": "nietoperz", + "🐻": "głowa niedźwiedzia", + "🐨": "koala", + "🦥": "leniwiec", + "🦦": "wydra", + "🦘": "kangur", + "🐥": "kurczątko od przodu", + "🐦️": "ptak", + "🕊️": "gołąb", + "🦆": "kaczka", + "🦉": "sowa", + "🦤": "dodo", + "🪶": "pióro", + "🦩": "flaming", + "🦚": "paw", + "🦜": "papuga", + "🐊": "krokodyl", + "🐢": "żółw", + "🦎": "jaszczurka", + "🐍": "wąż", + "🐲": "głowa smoka", + "🦕": "zauropod", + "🐳": "wieloryb tryskający wodą", + "🐬": "delfin", + "🦭": "foka", + "🐟️": "ryba", + "🐠": "ryba tropikalna", + "🦈": "rekin", + "🐙": "ośmiornica", + "🐚": "muszla spiralna", + "🐌": "ślimak", + "🦋": "motyl", + "🐛": "gąsienica", + "🐝": "pszczoła", + "🐞": "biedronka", + "💐": "bukiet kwiatów", + "🌹": "róża", + "🌺": "kwiat hibiskusa", + "🌻": "słonecznik", + "🌷": "tulipan", + "🌲": "wiecznie zielone drzewo", + "🌳": "drzewo liściaste", + "🌴": "palma", + "🌵": "kaktus", + "🌿": "zioło", + "🍁": "liść klonowy", + "🍇": "winogrona", + "🍈": "melon", + "🍉": "arbuz", + "🍊": "mandarynka", + "🍋": "cytryna", + "🍌": "banan", + "🍍": "ananas", + "🥭": "mango", + "🍏": "zielone jabłko", + "🍐": "gruszka", + "🍑": "brzoskwinia", + "🍒": "wiśnie", + "🍓": "truskawka", + "🫐": "borówka amerykańska", + "🥝": "owoc kiwi", + "🍅": "pomidor", + "🫒": "oliwka", + "🥥": "kokos", + "🥑": "awokado", + "🍆": "bakłażan", + "🥕": "marchew", + "🌽": "kolba kukurydzy", + "🌶️": "ostra papryka", + "🥬": "zielenina liściasta", + "🥦": "brokuł", + "🧅": "cebula", + "🍄": "grzyb", + "🥜": "orzeszki ziemne", + "🥐": "rogalik", + "🥖": "bagietka", + "🥨": "precel", + "🥯": "bajgiel", + "🥞": "naleśniki", + "🧇": "gofr", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "krab", + "🦞": "homar", + "🍨": "lody", + "🍩": "pączek z dziurką", + "🍪": "ciastko", + "🎂": "tort urodzinowy", + "🧁": "babeczka", + "🍫": "tabliczka czekolady", + "🍬": "cukierek", + "🍭": "lizak", + "🫖": "czajniczek", + "🧃": "napój w kartoniku", + "🧉": "yerba mate", + "🧭": "kompas", + "🏔️": "góra z czapą lodową", + "🌋": "wulkan", + "🏕️": "kemping", + "🏝️": "bezludna wyspa", + "🏡": "dom z ogrodem", + "⛲️": "fontanna", + "🎠": "koń z karuzeli", + "🎡": "diabelski młyn", + "🎢": "kolejka górska", + "💈": "słup fryzjerski", + "🚆": "pociąg", + "🚋": "wagon tramwajowy", + "🚍️": "nadjeżdżający autobus", + "🚕": "taksówka", + "🚗": "samochód", + "🚚": "samochód dostawczy", + "🚜": "ciągnik", + "🛵": "skuter", + "🛺": "autoriksza", + "🛴": "hulajnoga", + "🛹": "deskorolka", + "🛼": "wrotka", + "⚓️": "kotwica", + "⛵️": "żaglówka", + "🛶": "kajak", + "🚤": "ścigacz wodny", + "🚢": "statek", + "✈️": "samolot", + "🚁": "helikopter", + "🚠": "górska kolej linowa", + "🛰️": "satelita", + "🚀": "rakieta", + "🛸": "latający talerz", + "⏰": "budzik", + "🌙": "półksiężyc", + "🌡️": "termometr", + "☀️": "słońce", + "🪐": "planeta z pierścieniami", + "🌟": "błyszcząca gwiazda", + "🌀": "cyklon", + "🌈": "tęcza", + "☂️": "parasol", + "❄️": "płatek śniegu", + "☄️": "kometa", + "🔥": "ogień", + "💧": "kropla", + "🌊": "fala", + "🎃": "dynia na halloween", + "✨": "gwiazdki", + "🎈": "balon", + "🎉": "tuba z konfetti", + "🎏": "flaga w kształcie karpia", + "🎀": "wstążka", + "🎁": "zapakowany prezent", + "🎟️": "bilety wstępu", + "🏆️": "puchar", + "⚽️": "piłka nożna", + "🏀": "koszykówka", + "🏈": "futbol amerykański", + "🎾": "tenis", + "🥏": "frisbee", + "🏓": "tenis stołowy", + "🏸": "badminton", + "🤿": "maska do nurkowania", + "🥌": "kamień do curlingu", + "🎯": "strzał w 10", + "🪀": "jojo", + "🪁": "latawiec", + "🔮": "kryształowa kula", + "🎲": "kostka do gry", + "🧩": "element układanki", + "🎨": "paleta malarska", + "🧵": "nić", + "👕": "koszulka", + "🧦": "skarpetki", + "👗": "sukienka", + "🩳": "szorty", + "🎒": "tornister", + "👟": "but do biegania", + "👑": "korona", + "🧢": "bejsbolówka", + "💄": "szminka", + "💍": "pierścionek", + "💎": "kamień szlachetny", + "📢": "głośnik", + "🎶": "nuty", + "🎙️": "mikrofon studyjny", + "📻️": "radio", + "🎷": "saksofon", + "🪗": "akordeon", + "🎸": "gitara", + "🎺": "trąbka", + "🎻": "skrzypce", + "🪕": "banjo", + "🥁": "bęben", + "☎️": "telefon", + "🔋": "bateria", + "💿️": "dysk optyczny", + "🧮": "liczydło", + "🎬️": "klaps", + "💡": "żarówka", + "🔦": "latarka", + "🏮": "czerwony lampion", + "📕": "zamknięta książka", + "🏷️": "przywieszka do kluczy", + "💳️": "karta kredytowa", + "✏️": "ołówek", + "🖌️": "pędzel", + "🖍️": "kredka", + "📌": "pinezka", + "📎": "spinacz", + "🔑": "klucz", + "🪃": "bumerang", + "🏹": "łuk i strzała", + "⚖️": "waga szalkowa", + "🧲": "magnes", + "🧪": "probówka", + "🧬": "dna", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "antena satelitarna", + "🪑": "krzesło", + "🧹": "miotła", + "🗿": "moai" + }, + "pt": { + "👽️": "alienígena", + "🤖": "rosto de robô", + "🧠": "cérebro", + "👁️": "olho", + "🧙": "mago", + "🧚": "fada", + "🧜": "pessoa sereia", + "🐵": "rosto de macaco", + "🦧": "orangotango", + "🐶": "rosto de cachorro", + "🐺": "rosto de lobo", + "🦊": "rosto de raposa", + "🦝": "guaxinim", + "🐱": "rosto de gato", + "🦁": "rosto de leão", + "🐯": "rosto de tigre", + "🐴": "rosto de cavalo", + "🦄": "rosto de unicórnio", + "🦓": "zebra", + "🦌": "cervo", + "🐮": "rosto de vaca", + "🐷": "rosto de porco", + "🐗": "javali", + "🐪": "camelo", + "🦙": "lhama", + "🦒": "girafa", + "🐘": "elefante", + "🦣": "mamute", + "🦏": "rinoceronte", + "🐭": "rosto de camundongo", + "🐰": "rosto de coelho", + "🐿️": "esquilo", + "🦔": "porco-espinho", + "🦇": "morcego", + "🐻": "rosto de urso", + "🐨": "coala", + "🦥": "preguiça", + "🦦": "lontra", + "🦘": "canguru", + "🐥": "pintinho de frente", + "🐦️": "pássaro", + "🕊️": "pomba branca", + "🦆": "pato", + "🦉": "coruja", + "🦤": "dodô", + "🪶": "pena", + "🦩": "flamingo", + "🦚": "pavão", + "🦜": "papagaio", + "🐊": "crocodilo", + "🐢": "tartaruga", + "🦎": "lagartixa", + "🐍": "cobra", + "🐲": "rosto de dragão", + "🦕": "saurópode", + "🐳": "baleia esguichando água", + "🐬": "golfinho", + "🦭": "foca", + "🐟️": "peixe", + "🐠": "peixe tropical", + "🦈": "tubarão", + "🐙": "polvo", + "🐚": "caramujo", + "🐌": "caracol", + "🦋": "borboleta", + "🐛": "inseto", + "🐝": "abelha", + "🐞": "joaninha", + "💐": "buquê", + "🌹": "rosa", + "🌺": "hibisco", + "🌻": "girassol", + "🌷": "tulipa", + "🌲": "conífera", + "🌳": "árvore caidiça", + "🌴": "palmeira", + "🌵": "cacto", + "🌿": "erva", + "🍁": "folha de bordo", + "🍇": "uvas", + "🍈": "melão", + "🍉": "melancia", + "🍊": "tangerina", + "🍋": "limão", + "🍌": "banana", + "🍍": "abacaxi", + "🥭": "manga", + "🍏": "maçã verde", + "🍐": "pera", + "🍑": "pêssego", + "🍒": "cereja", + "🍓": "morango", + "🫐": "mirtilos", + "🥝": "kiwi", + "🍅": "tomate", + "🫒": "azeitona", + "🥥": "coco", + "🥑": "abacate", + "🍆": "berinjela", + "🥕": "cenoura", + "🌽": "milho", + "🌶️": "pimenta", + "🥬": "verdura", + "🥦": "brócolis", + "🧅": "cebola", + "🍄": "cogumelo", + "🥜": "amendoim", + "🥐": "croissant", + "🥖": "baguete", + "🥨": "pretzel", + "🥯": "rosca", + "🥞": "panquecas", + "🧇": "waffle", + "🍔": "hambúrguer", + "🍕": "pizza", + "🌭": "cachorro-quente", + "🌮": "taco", + "🍿": "pipoca", + "🦀": "caranguejo", + "🦞": "lagosta", + "🍨": "sorvete", + "🍩": "donut", + "🍪": "biscoito", + "🎂": "bolo de aniversário", + "🧁": "cupcake", + "🍫": "chocolate", + "🍬": "bala", + "🍭": "pirulito", + "🫖": "bule", + "🧃": "suco de caixa", + "🧉": "mate", + "🧭": "bússola", + "🏔️": "montanha com neve", + "🌋": "vulcão", + "🏕️": "acampamento", + "🏝️": "ilha deserta", + "🏡": "casa com jardim", + "⛲️": "fonte", + "🎠": "carrossel", + "🎡": "roda gigante", + "🎢": "montanha russa", + "💈": "barbearia", + "🚆": "trem", + "🚋": "bonde", + "🚍️": "ônibus se aproximando", + "🚕": "táxi", + "🚗": "carro", + "🚚": "caminhão de entrega", + "🚜": "trator", + "🛵": "scooter", + "🛺": "automóvel riquixá", + "🛴": "patinete", + "🛹": "skate", + "🛼": "patins de rodas", + "⚓️": "âncora", + "⛵️": "barco a vela", + "🛶": "canoa", + "🚤": "lancha", + "🚢": "navio", + "✈️": "avião", + "🚁": "helicóptero", + "🚠": "cabo suspenso usado em teleféricos nas montanhas", + "🛰️": "satélite", + "🚀": "foguete", + "🛸": "disco voador", + "⏰": "despertador", + "🌙": "lua crescente", + "🌡️": "termômetro", + "☀️": "sol", + "🪐": "planeta com anéis", + "🌟": "estrela brilhante", + "🌀": "ciclone", + "🌈": "arco-íris", + "☂️": "guarda-chuva", + "❄️": "floco de neve", + "☄️": "cometa", + "🔥": "fogo", + "💧": "gota", + "🌊": "onda", + "🎃": "abóbora de halloween", + "✨": "brilhos", + "🎈": "balão", + "🎉": "cone de festa", + "🎏": "bandeira de carpas", + "🎀": "laço de fita", + "🎁": "presente", + "🎟️": "ingresso de cinema", + "🏆️": "troféu", + "⚽️": "bola de futebol", + "🏀": "bola de basquete", + "🏈": "bola de futebol americano", + "🎾": "tênis", + "🥏": "frisbee", + "🏓": "pingue-pongue", + "🏸": "badminton", + "🤿": "máscara de mergulho", + "🥌": "pedra de curling", + "🎯": "no alvo", + "🪀": "ioiô", + "🪁": "pipa", + "🔮": "bola de cristal", + "🎲": "jogo de dado", + "🧩": "quebra-cabeça", + "🎨": "paleta de tintas", + "🧵": "carretel", + "👕": "camiseta", + "🧦": "meias", + "👗": "vestido", + "🩳": "shorts", + "🎒": "mochila", + "👟": "tênis de corrida", + "👑": "coroa", + "🧢": "boné", + "💄": "batom", + "💍": "anel", + "💎": "pedra preciosa", + "📢": "buzina", + "🎶": "notas musicais", + "🎙️": "microfone de estúdio", + "📻️": "rádio", + "🎷": "saxofone", + "🪗": "acordeão", + "🎸": "guitarra", + "🎺": "trompete", + "🎻": "violino", + "🪕": "banjo", + "🥁": "tambor", + "☎️": "telefone no gancho", + "🔋": "pilha", + "💿️": "cd", + "🧮": "ábaco", + "🎬️": "claquete", + "💡": "lâmpada", + "🔦": "lanterna", + "🏮": "lanterna vermelha de papel", + "📕": "livro fechado", + "🏷️": "etiqueta", + "💳️": "cartão de crédito", + "✏️": "lápis", + "🖌️": "pincel", + "🖍️": "giz de cera", + "📌": "tacha", + "📎": "clipe de papel", + "🔑": "chave", + "🪃": "bumerangue", + "🏹": "arco e flecha", + "⚖️": "balança", + "🧲": "ímã", + "🧪": "tubo de ensaio", + "🧬": "dna", + "🔬": "microscópio", + "🔭": "telescópio", + "📡": "antena parabólica", + "🪑": "cadeira", + "🧹": "vassoura", + "🗿": "moai" + }, + "ro": { + "👽️": "extraterestru", + "🤖": "robot", + "🧠": "creier", + "👁️": "un ochi", + "🧙": "mag", + "🧚": "zână", + "🧜": "persoană sirenă", + "🐵": "față de maimuță", + "🦧": "urangutan", + "🐶": "față de câine", + "🐺": "lup", + "🦊": "vulpe", + "🦝": "raton", + "🐱": "față de pisică", + "🦁": "leu", + "🐯": "față de tigru", + "🐴": "față de cal", + "🦄": "unicorn", + "🦓": "zebră", + "🦌": "cerb", + "🐮": "cap de vacă", + "🐷": "cap de porc", + "🐗": "mistreț", + "🐪": "dromader", + "🦙": "lamă", + "🦒": "girafă", + "🐘": "elefant", + "🦣": "mamut", + "🦏": "rinocer", + "🐭": "față de șoarece", + "🐰": "față de iepure", + "🐿️": "veveriță", + "🦔": "arici", + "🦇": "liliac", + "🐻": "urs", + "🐨": "koala", + "🦥": "leneș", + "🦦": "vidră", + "🦘": "cangur", + "🐥": "pui de găină din față", + "🐦️": "pasăre", + "🕊️": "porumbiță", + "🦆": "rață", + "🦉": "bufniță", + "🦤": "dodo", + "🪶": "pană", + "🦩": "flamingo", + "🦚": "păun", + "🦜": "papagal mare", + "🐊": "crocodil", + "🐢": "broască țestoasă", + "🦎": "șopârlă", + "🐍": "șarpe", + "🐲": "față de dragon", + "🦕": "sauropod", + "🐳": "balenă împroșcând apă", + "🐬": "delfin", + "🦭": "focă", + "🐟️": "pește", + "🐠": "pește tropical", + "🦈": "rechin", + "🐙": "caracatiță", + "🐚": "cochilie spiralată", + "🐌": "melc", + "🦋": "fluture", + "🐛": "gândac", + "🐝": "albină", + "🐞": "buburuză", + "💐": "buchet de flori", + "🌹": "trandafir", + "🌺": "hibiscus", + "🌻": "floarea soarelui", + "🌷": "lalea", + "🌲": "arbore conifer", + "🌳": "arbore foios", + "🌴": "palmier", + "🌵": "cactus", + "🌿": "plantă aromatică", + "🍁": "frunză de arțar", + "🍇": "struguri", + "🍈": "pepene galben", + "🍉": "pepene verde", + "🍊": "mandarină", + "🍋": "lămâie", + "🍌": "banană", + "🍍": "ananas", + "🥭": "mango", + "🍏": "măr verde", + "🍐": "pară", + "🍑": "piersică", + "🍒": "cireșe", + "🍓": "căpșună", + "🫐": "afine", + "🥝": "kiwi", + "🍅": "roșie", + "🫒": "măslină", + "🥥": "nucă de cocos", + "🥑": "avocado", + "🍆": "vânătă", + "🥕": "morcov", + "🌽": "știulete de porumb", + "🌶️": "ardei iute", + "🥬": "verdețuri", + "🥦": "broccoli", + "🧅": "ceapă", + "🍄": "ciupercă", + "🥜": "arahide", + "🥐": "croasant", + "🥖": "baghetă de pâine", + "🥨": "covrig", + "🥯": "bagel", + "🥞": "clătite", + "🧇": "gofră", + "🍔": "hamburger", + "🍕": "pizza", + "🌭": "hot dog", + "🌮": "taco", + "🍿": "floricele de porumb", + "🦀": "rac", + "🦞": "homar", + "🍨": "înghețată", + "🍩": "gogoașă americană", + "🍪": "fursec", + "🎂": "tort aniversar", + "🧁": "brioșă", + "🍫": "baton de ciocolată", + "🍬": "bomboană", + "🍭": "acadea", + "🫖": "ceainic", + "🧃": "cutie de băutură", + "🧉": "mate", + "🧭": "busolă", + "🏔️": "vârf de munte înzăpezit", + "🌋": "vulcan", + "🏕️": "camping", + "🏝️": "insulă pustie", + "🏡": "casă cu grădină", + "⛲️": "fântână arteziană", + "🎠": "căluț de carusel", + "🎡": "roata mare", + "🎢": "montagne russe", + "💈": "semn de frizerie", + "🚆": "tren", + "🚋": "vagon de tramvai", + "🚍️": "autobuz care se apropie", + "🚕": "taxi", + "🚗": "automobil", + "🚚": "camion de marfă", + "🚜": "tractor", + "🛵": "scuter", + "🛺": "autoricșă", + "🛴": "trotinetă", + "🛹": "skateboard", + "🛼": "patină cu rotile", + "⚓️": "ancoră", + "⛵️": "barcă cu pânze", + "🛶": "canoe", + "🚤": "barcă de viteză", + "🚢": "vapor", + "✈️": "avion", + "🚁": "elicopter", + "🚠": "telecabină montană", + "🛰️": "satelit", + "🚀": "rachetă", + "🛸": "farfurie zburătoare", + "⏰": "ceas deșteptător", + "🌙": "semilună", + "🌡️": "termometru", + "☀️": "soare", + "🪐": "planetă cu inele", + "🌟": "stea strălucitoare", + "🌀": "ciclon", + "🌈": "curcubeu", + "☂️": "umbrelă", + "❄️": "fulg de nea", + "☄️": "cometă", + "🔥": "foc", + "💧": "picătură", + "🌊": "val", + "🎃": "dovleac cu felinar", + "✨": "steluțe", + "🎈": "balon", + "🎉": "tun cu confetti", + "🎏": "crap decorativ în vânt", + "🎀": "fundă", + "🎁": "cadou împachetat", + "🎟️": "bilete de intrare", + "🏆️": "trofeu", + "⚽️": "minge de fotbal", + "🏀": "baschet", + "🏈": "fotbal american", + "🎾": "tenis", + "🥏": "disc zburător", + "🏓": "ping pong", + "🏸": "badminton", + "🤿": "mască de scufundări", + "🥌": "piatră de curling", + "🎯": "fix la țintă", + "🪀": "yo-yo", + "🪁": "zmeu", + "🔮": "glob de cristal", + "🎲": "zar", + "🧩": "piesă de puzzle", + "🎨": "paletă de pictor", + "🧵": "ață", + "👕": "tricou", + "🧦": "șosete", + "👗": "rochie", + "🩳": "șort", + "🎒": "rucsac", + "👟": "pantof sport", + "👑": "coroană", + "🧢": "șapcă", + "💄": "ruj", + "💍": "inel", + "💎": "piatră prețioasă", + "📢": "portavoce", + "🎶": "note muzicale", + "🎙️": "microfon de studio", + "📻️": "radio", + "🎷": "saxofon", + "🪗": "acordeon", + "🎸": "chitară", + "🎺": "trompetă", + "🎻": "vioară", + "🪕": "banjo", + "🥁": "tobă", + "☎️": "telefon", + "🔋": "baterie", + "💿️": "cd", + "🧮": "abac", + "🎬️": "clachetă", + "💡": "bec", + "🔦": "lanternă", + "🏮": "felinar din hârtie roșie", + "📕": "carte închisă", + "🏷️": "etichetă", + "💳️": "card de credit", + "✏️": "creion", + "🖌️": "pensulă", + "🖍️": "creion colorat", + "📌": "piuneză", + "📎": "agrafă de hârtie", + "🔑": "cheie", + "🪃": "bumerang", + "🏹": "arc și săgeată", + "⚖️": "balanță", + "🧲": "magnet", + "🧪": "eprubetă", + "🧬": "ADN", + "🔬": "microscop", + "🔭": "telescop", + "📡": "antenă satelit", + "🪑": "scaun", + "🧹": "mătură", + "🗿": "statuie moai" + }, + "ru": { + "👽️": "инопланетянин", + "🤖": "робот", + "🧠": "мозг", + "👁️": "глаз", + "🧙": "маг", + "🧚": "фея", + "🧜": "русалка", + "🐵": "морда обезьяны", + "🦧": "орангутан", + "🐶": "морда собаки", + "🐺": "морда волка", + "🦊": "морда лисицы", + "🦝": "морда енота", + "🐱": "морда кошки", + "🦁": "морда льва", + "🐯": "морда тигра", + "🐴": "морда лошади", + "🦄": "голова единорога", + "🦓": "зебра", + "🦌": "олень", + "🐮": "морда коровы", + "🐷": "морда свиньи", + "🐗": "кабан", + "🐪": "одногорбый верблюд", + "🦙": "лама", + "🦒": "жираф", + "🐘": "слон", + "🦣": "мамонт", + "🦏": "носорог", + "🐭": "морда мыши", + "🐰": "морда кролика", + "🐿️": "бурундук", + "🦔": "еж", + "🦇": "летучая мышь", + "🐻": "морда медведя", + "🐨": "морда коалы", + "🦥": "ленивец", + "🦦": "выдра", + "🦘": "кенгуру", + "🐥": "цыпленок", + "🐦️": "птица", + "🕊️": "голубь", + "🦆": "утка", + "🦉": "сова", + "🦤": "дронт", + "🪶": "перо", + "🦩": "фламинго", + "🦚": "павлин", + "🦜": "попугай", + "🐊": "крокодил", + "🐢": "черепаха", + "🦎": "ящерица", + "🐍": "змея", + "🐲": "голова дракона", + "🦕": "зауропод", + "🐳": "кит с фонтанчиком", + "🐬": "дельфин", + "🦭": "тюлень", + "🐟️": "рыба", + "🐠": "тропическая рыба", + "🦈": "акула", + "🐙": "осьминог", + "🐚": "раковина", + "🐌": "улитка", + "🦋": "бабочка", + "🐛": "гусеница", + "🐝": "пчела", + "🐞": "божья коровка", + "💐": "букет", + "🌹": "роза", + "🌺": "гибискус", + "🌻": "подсолнух", + "🌷": "тюльпан", + "🌲": "елка", + "🌳": "дерево", + "🌴": "пальма", + "🌵": "кактус", + "🌿": "веточка", + "🍁": "кленовый лист", + "🍇": "виноград", + "🍈": "дыня", + "🍉": "арбуз", + "🍊": "мандарин", + "🍋": "лимон", + "🍌": "банан", + "🍍": "ананас", + "🥭": "манго", + "🍏": "зеленое яблоко", + "🍐": "груша", + "🍑": "персик", + "🍒": "вишня", + "🍓": "клубника", + "🫐": "голубика", + "🥝": "киви", + "🍅": "помидор", + "🫒": "оливка", + "🥥": "кокос", + "🥑": "авокадо", + "🍆": "баклажан", + "🥕": "морковь", + "🌽": "кукурузный початок", + "🌶️": "острый перец", + "🥬": "зеленый салат", + "🥦": "брокколи", + "🧅": "лук", + "🍄": "гриб", + "🥜": "арахис", + "🥐": "круассан", + "🥖": "багет", + "🥨": "крендель", + "🥯": "бублик", + "🥞": "блины", + "🧇": "вафля", + "🍔": "гамбургер", + "🍕": "пицца", + "🌭": "хот-дог", + "🌮": "тако", + "🍿": "попкорн", + "🦀": "краб", + "🦞": "омар", + "🍨": "мороженое", + "🍩": "пончик", + "🍪": "печенье", + "🎂": "торт ко дню рождения", + "🧁": "капкейк", + "🍫": "шоколад", + "🍬": "конфета", + "🍭": "леденец", + "🫖": "чайник", + "🧃": "пакетик сока", + "🧉": "мате", + "🧭": "компас", + "🏔️": "гора со снежной шапкой", + "🌋": "вулкан", + "🏕️": "кемпинг", + "🏝️": "остров", + "🏡": "дом с садом", + "⛲️": "фонтан", + "🎠": "лошадь на карусели", + "🎡": "колесо обозрения", + "🎢": "американские горки", + "💈": "парикмахерская", + "🚆": "поезд", + "🚋": "трамвайный вагон", + "🚍️": "автобус спереди", + "🚕": "такси", + "🚗": "автомобиль", + "🚚": "грузовик", + "🚜": "трактор", + "🛵": "скутер", + "🛺": "авторикша", + "🛴": "самокат", + "🛹": "скейтборд", + "🛼": "роликовые коньки", + "⚓️": "якорь", + "⛵️": "парусник", + "🛶": "каноэ", + "🚤": "катер", + "🚢": "корабль", + "✈️": "самолет", + "🚁": "вертолет", + "🚠": "вагон фуникулера", + "🛰️": "спутник", + "🚀": "ракета", + "🛸": "летающая тарелка", + "⏰": "будильник", + "🌙": "полумесяц", + "🌡️": "термометр", + "☀️": "солнце", + "🪐": "планета с кольцом", + "🌟": "сияющая звезда", + "🌀": "циклон", + "🌈": "радуга", + "☂️": "зонт", + "❄️": "снежинка", + "☄️": "комета", + "🔥": "огонь", + "💧": "капля", + "🌊": "волна", + "🎃": "тыква-фонарь", + "✨": "блестки", + "🎈": "воздушный шарик", + "🎉": "хлопушка", + "🎏": "вымпелы в виде карпов", + "🎀": "бантик", + "🎁": "подарок", + "🎟️": "билеты", + "🏆️": "кубок", + "⚽️": "футбол", + "🏀": "баскетбол", + "🏈": "американский футбол", + "🎾": "теннис", + "🥏": "летающий диск", + "🏓": "настольный теннис", + "🏸": "бадминтон", + "🤿": "маска с трубкой", + "🥌": "керлинг", + "🎯": "мишень", + "🪀": "йо-йо", + "🪁": "воздушный змей", + "🔮": "хрустальный шар", + "🎲": "игральная кость", + "🧩": "пазл", + "🎨": "палитра с красками", + "🧵": "катушка ниток", + "👕": "футболка", + "🧦": "носки", + "👗": "платье", + "🩳": "шорты", + "🎒": "ранец", + "👟": "кроссовки", + "👑": "корона", + "🧢": "бейсболка", + "💄": "помада", + "💍": "кольцо", + "💎": "драгоценный камень", + "📢": "громкоговоритель", + "🎶": "ноты", + "🎙️": "студийный микрофон", + "📻️": "радио", + "🎷": "саксофон", + "🪗": "аккордеон", + "🎸": "гитара", + "🎺": "труба", + "🎻": "скрипка", + "🪕": "банджо", + "🥁": "барабан", + "☎️": "телефон", + "🔋": "батарейка", + "💿️": "CD", + "🧮": "счеты", + "🎬️": "хлопушка-нумератор", + "💡": "лампочка", + "🔦": "фонарик", + "🏮": "красный бумажный фонарик", + "📕": "закрытая книга", + "🏷️": "бирка", + "💳️": "кредитная карта", + "✏️": "карандаш", + "🖌️": "кисточка", + "🖍️": "восковой мелок", + "📌": "канцелярская кнопка", + "📎": "скрепка", + "🔑": "ключ", + "🪃": "бумеранг", + "🏹": "лук со стрелой", + "⚖️": "весы", + "🧲": "магнит", + "🧪": "пробирка", + "🧬": "ДНК", + "🔬": "микроскоп", + "🔭": "телескоп", + "📡": "спутниковая антенна", + "🪑": "стул", + "🧹": "метла", + "🗿": "истукан" + }, + "sv": { + "👽️": "utomjording", + "🤖": "robotansikte", + "🧠": "hjärna", + "👁️": "öga", + "🧙": "magiker", + "🧚": "fé", + "🧜": "vattenväsen", + "🐵": "apansikte", + "🦧": "orangutang", + "🐶": "hundansikte", + "🐺": "vargansikte", + "🦊": "rävansikte", + "🦝": "tvättbjörn", + "🐱": "kattansikte", + "🦁": "lejonansikte", + "🐯": "tigeransikte", + "🐴": "hästansikte", + "🦄": "enhörningsansikte", + "🦓": "zebra", + "🦌": "hjort", + "🐮": "koansikte", + "🐷": "grisansikte", + "🐗": "vildsvin", + "🐪": "dromedar", + "🦙": "lama", + "🦒": "giraff", + "🐘": "elefant", + "🦣": "mammut", + "🦏": "noshörning", + "🐭": "musansikte", + "🐰": "kaninansikte", + "🐿️": "jordekorre", + "🦔": "igelkott", + "🦇": "fladdermus", + "🐻": "björnansikte", + "🐨": "koala", + "🦥": "sengångare", + "🦦": "utter", + "🦘": "känguru", + "🐥": "kyckling framifrån", + "🐦️": "fågel", + "🕊️": "duva", + "🦆": "and", + "🦉": "uggla", + "🦤": "dront", + "🪶": "fågelfjäder", + "🦩": "flamingo", + "🦚": "påfågel", + "🦜": "papegoja", + "🐊": "krokodil", + "🐢": "sköldpadda", + "🦎": "ödla", + "🐍": "orm", + "🐲": "drakansikte", + "🦕": "sauropod", + "🐳": "val som sprutar vatten", + "🐬": "delfin", + "🦭": "säl", + "🐟️": "fisk", + "🐠": "tropisk fisk", + "🦈": "haj", + "🐙": "bläckfisk", + "🐚": "snäcka", + "🐌": "snigel", + "🦋": "fjäril", + "🐛": "tusenfoting", + "🐝": "bi", + "🐞": "nyckelpiga", + "💐": "blombukett", + "🌹": "ros", + "🌺": "hibiskus", + "🌻": "solros", + "🌷": "tulpan", + "🌲": "barrträd", + "🌳": "lövträd", + "🌴": "palm", + "🌵": "kaktus", + "🌿": "ört", + "🍁": "lönnlöv", + "🍇": "vindruvor", + "🍈": "melon", + "🍉": "vattenmelon", + "🍊": "mandarin", + "🍋": "citron", + "🍌": "banan", + "🍍": "ananas", + "🥭": "mango", + "🍏": "grönt äpple", + "🍐": "päron", + "🍑": "persika", + "🍒": "körsbär", + "🍓": "jordgubbe", + "🫐": "blåbär", + "🥝": "kiwifrukt", + "🍅": "tomat", + "🫒": "oliv", + "🥥": "kokosnöt", + "🥑": "avokado", + "🍆": "aubergine", + "🥕": "morot", + "🌽": "majskolv", + "🌶️": "chili", + "🥬": "sallat", + "🥦": "broccoli", + "🧅": "lök", + "🍄": "svamp", + "🥜": "jordnötter", + "🥐": "croissant", + "🥖": "baguette", + "🥨": "pretzel", + "🥯": "bagel", + "🥞": "pannkakor", + "🧇": "våffla", + "🍔": "hamburgare", + "🍕": "pizzabit", + "🌭": "varmkorv", + "🌮": "taco", + "🍿": "popcorn", + "🦀": "krabba", + "🦞": "hummer", + "🍨": "glass", + "🍩": "munk", + "🍪": "kaka", + "🎂": "födelsedagstårta", + "🧁": "muffin", + "🍫": "chokladkaka", + "🍬": "godis", + "🍭": "klubba", + "🫖": "tekanna", + "🧃": "dryckeskartong", + "🧉": "mate-te", + "🧭": "kompass", + "🏔️": "snöklätt berg", + "🌋": "vulkan", + "🏕️": "camping", + "🏝️": "öde ö", + "🏡": "hus med trädgård", + "⛲️": "fontän", + "🎠": "karusellhäst", + "🎡": "pariserhjul", + "🎢": "berg- och dalbana", + "💈": "barberarstolpe", + "🚆": "tåg", + "🚋": "spårvagnsvagn", + "🚍️": "mötande buss", + "🚕": "taxi", + "🚗": "bil", + "🚚": "budbil", + "🚜": "traktor", + "🛵": "skoter", + "🛺": "automatisk riksha", + "🛴": "sparkcykel", + "🛹": "skateboard", + "🛼": "rullskridsko", + "⚓️": "ankare", + "⛵️": "segelbåt", + "🛶": "kanot", + "🚤": "racerbåt", + "🚢": "fartyg", + "✈️": "flygplan", + "🚁": "helikopter", + "🚠": "linbana", + "🛰️": "satellit", + "🚀": "raket", + "🛸": "flygande tefat", + "⏰": "väckarklocka", + "🌙": "månskära", + "🌡️": "termometer", + "☀️": "sol", + "🪐": "planet med ringar", + "🌟": "blänkande stjärna", + "🌀": "cyklon", + "🌈": "regnbåge", + "☂️": "paraply", + "❄️": "snöflinga", + "☄️": "komet", + "🔥": "eld", + "💧": "droppe", + "🌊": "våg", + "🎃": "lyktgubbe", + "✨": "gnistrande stjärnor", + "🎈": "ballong", + "🎉": "konfettismällare", + "🎏": "karpvindstrut", + "🎀": "rosett", + "🎁": "inslagen present", + "🎟️": "inträdesbiljetter", + "🏆️": "pokal", + "⚽️": "fotboll", + "🏀": "basketboll", + "🏈": "amerikansk fotboll", + "🎾": "tennis", + "🥏": "frisbee", + "🏓": "bordtennis", + "🏸": "badminton", + "🤿": "dykmask", + "🥌": "curlingsten", + "🎯": "mitt i prick", + "🪀": "jojo", + "🪁": "flygande drake", + "🔮": "kristallkula", + "🎲": "speltärning", + "🧩": "pusselbit", + "🎨": "färgpalett", + "🧵": "tråd", + "👕": "t-shirt", + "🧦": "sockor", + "👗": "klänning", + "🩳": "shorts", + "🎒": "ryggsäck", + "👟": "gympasko", + "👑": "krona", + "🧢": "skärmmössa", + "💄": "läppstift", + "💍": "ring", + "💎": "ädelsten", + "📢": "handhögtalare", + "🎶": "musiknoter", + "🎙️": "studiomikrofon", + "📻️": "radio", + "🎷": "saxofon", + "🪗": "dragspel", + "🎸": "gitarr", + "🎺": "trumpet", + "🎻": "fiol", + "🪕": "banjo", + "🥁": "trumma", + "☎️": "telefon", + "🔋": "batteri", + "💿️": "optisk skiva", + "🧮": "kulram", + "🎬️": "filmklappa", + "💡": "glödlampa", + "🔦": "ficklampa", + "🏮": "röd papperslykta", + "📕": "stängd bok", + "🏷️": "etikett", + "💳️": "kreditkort", + "✏️": "blyertspenna", + "🖌️": "målarpensel", + "🖍️": "krita", + "📌": "kartnål", + "📎": "gem", + "🔑": "nyckel", + "🪃": "boomerang", + "🏹": "pil och båge", + "⚖️": "balansvåg", + "🧲": "magnet", + "🧪": "provrör", + "🧬": "DNA", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "tv-satellit", + "🪑": "stol", + "🧹": "kvast", + "🗿": "staty" + }, + "th": { + "👽️": "เอเลี่ยน", + "🤖": "หุ่นยนต์", + "🧠": "สมอง", + "👁️": "ตาข้างเดียว", + "🧙": "นักเวทย์", + "🧚": "นางฟ้า", + "🧜": "ครึ่งคนครึ่งปลา", + "🐵": "หน้าลิง", + "🦧": "อุรังอุตัง", + "🐶": "หน้าสุนัข", + "🐺": "หมาป่า", + "🦊": "จิ้งจอก", + "🦝": "แรคคูน", + "🐱": "หน้าแมว", + "🦁": "สิงโต", + "🐯": "หน้าเสือ", + "🐴": "หน้าม้า", + "🦄": "ยูนิคอร์น", + "🦓": "ม้าลาย", + "🦌": "กวาง", + "🐮": "หน้าวัว", + "🐷": "หน้าหมู", + "🐗": "หมูป่าตัวผู้", + "🐪": "อูฐโหนกเดียว", + "🦙": "ลามะ", + "🦒": "ยีราฟ", + "🐘": "ช้าง", + "🦣": "ช้างแมมมอธ", + "🦏": "แรด", + "🐭": "หน้าหนู", + "🐰": "หน้ากระต่าย", + "🐿️": "ชิปมังก์", + "🦔": "เฮดจ์ฮ็อก", + "🦇": "ค้างคาว", + "🐻": "หมี", + "🐨": "โคอาล่า", + "🦥": "สลอธ", + "🦦": "ตัวนาก", + "🦘": "จิงโจ้", + "🐥": "ลูกเจี๊ยบยืนหันหน้า", + "🐦️": "นก", + "🕊️": "นกพิราบขาว", + "🦆": "เป็ด", + "🦉": "นกฮูก", + "🦤": "นกโดโด", + "🪶": "ขนนก", + "🦩": "นกฟลามิงโก", + "🦚": "นกยูง", + "🦜": "นกแก้ว", + "🐊": "จระเข้", + "🐢": "เต่า", + "🦎": "จิ้งจก", + "🐍": "งู", + "🐲": "หน้ามังกร", + "🦕": "ไดโนเสาร์", + "🐳": "ปลาวาฬพ่นน้ำ", + "🐬": "ปลาโลมา", + "🦭": "แมวน้ำ", + "🐟️": "ปลา", + "🐠": "ปลาเขตร้อน", + "🦈": "ฉลาม", + "🐙": "ปลาหมึกยักษ์", + "🐚": "หอย", + "🐌": "หอยทาก", + "🦋": "ผีเสื้อ", + "🐛": "แมลง", + "🐝": "ผึ้ง", + "🐞": "เต่าทอง", + "💐": "ช่อดอกไม้", + "🌹": "ดอกกุหลาบ", + "🌺": "ดอกชบา", + "🌻": "ดอกทานตะวัน", + "🌷": "ทิวลิป", + "🌲": "ต้นสน", + "🌳": "ต้นไม้ร่มรื่น", + "🌴": "ต้นมะพร้าว", + "🌵": "ตะบองเพชร", + "🌿": "สมุนไพร", + "🍁": "ใบเมเปิ้ล", + "🍇": "องุ่น", + "🍈": "เมลอน", + "🍉": "แตงโม", + "🍊": "ส้ม", + "🍋": "เลมอน", + "🍌": "กล้วย", + "🍍": "สับปะรด", + "🥭": "มะม่วง", + "🍏": "แอปเปิ้ลเขียว", + "🍐": "ลูกแพร์", + "🍑": "ลูกพีช", + "🍒": "เชอร์รี่", + "🍓": "สตรอว์เบอร์รี่", + "🫐": "บลูเบอร์รี่", + "🥝": "กีวี", + "🍅": "มะเขือเทศ", + "🫒": "มะกอก", + "🥥": "มะพร้าว", + "🥑": "อาโวคาโด", + "🍆": "มะเขือยาว", + "🥕": "แครอท", + "🌽": "ข้าวโพด", + "🌶️": "พริก", + "🥬": "ผักใบเขียว", + "🥦": "บรอกโคลี", + "🧅": "หอมหัวใหญ่", + "🍄": "เห็ด", + "🥜": "ถั่ว", + "🥐": "ครัวซอง", + "🥖": "ขนมปังฝรั่งเศส", + "🥨": "เพรตเซล", + "🥯": "เบเกิล", + "🥞": "แพนเค้ก", + "🧇": "วาฟเฟิล", + "🍔": "แฮมเบอร์เกอร์", + "🍕": "พิซซ่า 1 ชิ้น", + "🌭": "ฮอทด็อก", + "🌮": "ทาโก้", + "🍿": "ป๊อปคอร์น", + "🦀": "ปู", + "🦞": "กุ้งมังกร", + "🍨": "ไอศกรีม", + "🍩": "โดนัท", + "🍪": "คุกกี้", + "🎂": "เค้กวันเกิด", + "🧁": "คัพเค้ก", + "🍫": "ช็อกโกแลต", + "🍬": "ลูกอม", + "🍭": "อมยิ้ม", + "🫖": "กาน้ำชา", + "🧃": "เครื่องดื่มแบบกล่อง", + "🧉": "ชามาเต", + "🧭": "เข็มทิศ", + "🏔️": "ภูเขามีหิมะ", + "🌋": "ภูเขาไฟ", + "🏕️": "ตั้งแคมป์", + "🏝️": "เกาะ", + "🏡": "บ้านพร้อมสวน", + "⛲️": "น้ำพุ", + "🎠": "ม้าหมุน", + "🎡": "ชิงช้าสวรรค์", + "🎢": "รถไฟเหาะ", + "💈": "ร้านตัดผม", + "🚆": "รถไฟ", + "🚋": "ตู้รถราง", + "🚍️": "รถบัสกำลังมา", + "🚕": "แท็กซี่", + "🚗": "รถ", + "🚚": "รถขนส่ง", + "🚜": "แทร็กเตอร์", + "🛵": "รถสกู๊ตเตอร์", + "🛺": "รถสามล้อ", + "🛴": "สกู๊ตเตอร์", + "🛹": "สเก็ตบอร์ด", + "🛼": "รองเท้าสเก็ต", + "⚓️": "สมอเรือ", + "⛵️": "เรือใบ", + "🛶": "แคนู", + "🚤": "เรือด่วน", + "🚢": "เรือ", + "✈️": "เครื่องบิน", + "🚁": "เฮลิคอปเตอร์", + "🚠": "เคเบิลคาร์", + "🛰️": "ดาวเทียม", + "🚀": "จรวด", + "🛸": "จานบิน", + "⏰": "นาฬิกาปลุก", + "🌙": "พระจันทร์เสี้ยว", + "🌡️": "เครื่องวัดอุณหภูมิ", + "☀️": "พระอาทิตย์", + "🪐": "ดาวเคราะห์ที่มีวงแหวน", + "🌟": "ดาวส่องแสง", + "🌀": "ไซโคลน", + "🌈": "รุ้ง", + "☂️": "ร่ม", + "❄️": "เกล็ดหิมะ", + "☄️": "ดาวหาง", + "🔥": "ไฟ", + "💧": "หยดน้ำ", + "🌊": "คลื่น", + "🎃": "ฟักทองฮาโลวีน", + "✨": "ประกายวิบวับ", + "🎈": "ลูกโป่ง", + "🎉": "ปาร์ตี้", + "🎏": "ธงปลาคาร์พ", + "🎀": "ริบบิ้น", + "🎁": "ของขวัญ", + "🎟️": "ตั๋วเข้าชม", + "🏆️": "ถ้วยรางวัล", + "⚽️": "ลูกฟุตบอล", + "🏀": "บาสเกตบอล", + "🏈": "อเมริกันฟุตบอล", + "🎾": "เทนนิส", + "🥏": "จานร่อน", + "🏓": "ปิงปอง", + "🏸": "แบดมินตัน", + "🤿": "หน้ากากดำน้ำ", + "🥌": "ลูกกลิ้งหิน", + "🎯": "กลางเป้า", + "🪀": "โยโย่", + "🪁": "ว่าว", + "🔮": "ลูกแก้ววิเศษ", + "🎲": "ลูกเต๋า", + "🧩": "จิ๊กซอว์", + "🎨": "จานสีวาดรูป", + "🧵": "ด้าย", + "👕": "เสื้อยืด", + "🧦": "ถุงเท้า", + "👗": "ชุดกระโปรง", + "🩳": "กางเกงขาสั้น", + "🎒": "เป้นักเรียน", + "👟": "รองเท้ากีฬา", + "👑": "มงกุฎ", + "🧢": "หมวกแก๊ป", + "💄": "ลิปสติก", + "💍": "แหวน", + "💎": "อัญมณี", + "📢": "เครื่องขยายเสียง", + "🎶": "โน้ตดนตรีหลายตัว", + "🎙️": "ไมค์สตูดิโอ", + "📻️": "วิทยุ", + "🎷": "แซ็กโซโฟน", + "🪗": "แอคคอร์เดียน", + "🎸": "กีต้าร์", + "🎺": "ทรัมเป็ต", + "🎻": "ไวโอลิน", + "🪕": "แบนโจ", + "🥁": "กลอง", + "☎️": "โทรศัพท์", + "🔋": "แบตเตอรี่", + "💿️": "บลูเรย์", + "🧮": "ลูกคิด", + "🎬️": "สเลท", + "💡": "หลอดไฟ", + "🔦": "ไฟฉาย", + "🏮": "โคมไฟแดง", + "📕": "หนังสือปิด", + "🏷️": "ป้าย", + "💳️": "บัตรเครดิต", + "✏️": "ดินสอ", + "🖌️": "แปรงทาสี", + "🖍️": "ดินสอสี", + "📌": "หมุดปัก", + "📎": "คลิปหนีบกระดาษ", + "🔑": "กุญแจ", + "🪃": "บูมเมอแรง", + "🏹": "ธนูและลูกศร", + "⚖️": "ตราชั่ง", + "🧲": "แม่เหล็ก", + "🧪": "หลอดทดลอง", + "🧬": "ดีเอ็นเอ", + "🔬": "กล้องจุลทรรศน์", + "🔭": "กล้องโทรทรรศน์", + "📡": "จานดาวเทียม", + "🪑": "เก้าอี้", + "🧹": "ไม้กวาด", + "🗿": "รูปปั้นโมไอ" + }, + "tr": { + "👽️": "uzaylı", + "🤖": "robot", + "🧠": "beyin", + "👁️": "göz", + "🧙": "büyücü", + "🧚": "peri", + "🧜": "balık insan", + "🐵": "maymun yüzü", + "🦧": "orangutan", + "🐶": "köpek yüzü", + "🐺": "kurt", + "🦊": "tilki", + "🦝": "rakun", + "🐱": "kedi yüzü", + "🦁": "aslan", + "🐯": "kaplan yüzü", + "🐴": "at yüzü", + "🦄": "tek boynuzlu at", + "🦓": "zebra", + "🦌": "geyik", + "🐮": "inek yüzü", + "🐷": "domuz yüzü", + "🐗": "yaban domuzu", + "🐪": "deve", + "🦙": "lama", + "🦒": "zürafa", + "🐘": "fil", + "🦣": "mamut", + "🦏": "gergedan", + "🐭": "fare yüzü", + "🐰": "tavşan yüzü", + "🐿️": "sincap", + "🦔": "kirpi", + "🦇": "yarasa", + "🐻": "ayı", + "🐨": "koala", + "🦥": "tembel hayvan", + "🦦": "su samuru", + "🦘": "kanguru", + "🐥": "önden civciv", + "🐦️": "kuş", + "🕊️": "güvercin", + "🦆": "ördek", + "🦉": "baykuş", + "🦤": "dodo kuşu", + "🪶": "kuş tüyü", + "🦩": "flamingo", + "🦚": "tavus kuşu", + "🦜": "papağan", + "🐊": "timsah", + "🐢": "kaplumbağa", + "🦎": "kertenkele", + "🐍": "yılan", + "🐲": "ejderha yüzü", + "🦕": "soropod", + "🐳": "su püskürten balina", + "🐬": "yunus", + "🦭": "fok", + "🐟️": "balık", + "🐠": "tropikal balık", + "🦈": "köpek balığı", + "🐙": "ahtapot", + "🐚": "deniz kabuğu", + "🐌": "salyangoz", + "🦋": "kelebek", + "🐛": "tırtıl", + "🐝": "bal arısı", + "🐞": "uğur böceği", + "💐": "buket", + "🌹": "gül", + "🌺": "çingülü", + "🌻": "ayçiçeği", + "🌷": "lale", + "🌲": "yaprak dökmeyen ağaç", + "🌳": "yaprak döken ağaç", + "🌴": "palmiye ağacı", + "🌵": "kaktüs", + "🌿": "ot", + "🍁": "akçaağaç yaprağı", + "🍇": "üzüm", + "🍈": "kavun", + "🍉": "karpuz", + "🍊": "mandalina", + "🍋": "limon", + "🍌": "muz", + "🍍": "ananas", + "🥭": "mango", + "🍏": "yeşil elma", + "🍐": "armut", + "🍑": "şeftali", + "🍒": "kiraz", + "🍓": "çilek", + "🫐": "yaban mersini", + "🥝": "kivi", + "🍅": "domates", + "🫒": "zeytin", + "🥥": "Hindistan cevizi", + "🥑": "avokado", + "🍆": "patlıcan", + "🥕": "havuç", + "🌽": "mısır koçanı", + "🌶️": "acı biber", + "🥬": "yeşil yapraklı sebze", + "🥦": "brokoli", + "🧅": "soğan", + "🍄": "mantar", + "🥜": "yer fıstığı", + "🥐": "kruvasan", + "🥖": "baget ekmek", + "🥨": "pretzel", + "🥯": "simit", + "🥞": "krep", + "🧇": "waffle", + "🍔": "hamburger", + "🍕": "pizza dilimi", + "🌭": "sosisli sandviç", + "🌮": "tako", + "🍿": "patlamış mısır", + "🦀": "yengeç", + "🦞": "ıstakoz", + "🍨": "dondurma", + "🍩": "donut", + "🍪": "kurabiye", + "🎂": "doğum günü pastası", + "🧁": "cupcake", + "🍫": "çikolata", + "🍬": "şekerleme", + "🍭": "lolipop", + "🫖": "demlik", + "🧃": "içecek kutusu", + "🧉": "mate çayı", + "🧭": "pusula", + "🏔️": "karla kaplı dağ", + "🌋": "yanardağ", + "🏕️": "kamp", + "🏝️": "ıssız ada", + "🏡": "bahçeli ev", + "⛲️": "fıskiye", + "🎠": "atlıkarınca", + "🎡": "dönme dolap", + "🎢": "hız treni", + "💈": "berber dükkanı", + "🚆": "tren", + "🚋": "tramvay vagonu", + "🚍️": "yaklaşan otobüs", + "🚕": "taksi", + "🚗": "araba", + "🚚": "nakliye kamyonu", + "🚜": "traktör", + "🛵": "motorlu skuter", + "🛺": "elektrikli çekçek", + "🛴": "ayakla sürülen skuter", + "🛹": "kaykay", + "🛼": "paten", + "⚓️": "çıpa", + "⛵️": "yelkenli tekne", + "🛶": "kano", + "🚤": "sürat teknesi", + "🚢": "gemi", + "✈️": "uçak", + "🚁": "helikopter", + "🚠": "dağ teleferik hattı", + "🛰️": "uydu", + "🚀": "roket", + "🛸": "uçan daire", + "⏰": "çalar saat", + "🌙": "hilal", + "🌡️": "termometre", + "☀️": "güneş", + "🪐": "halkalı gezegen", + "🌟": "parıldayan yıldız", + "🌀": "siklon", + "🌈": "gökkuşağı", + "☂️": "şemsiye", + "❄️": "kar tanesi", + "☄️": "kuyruklu yıldız", + "🔥": "ateş", + "💧": "damlacık", + "🌊": "dalga", + "🎃": "kabak fener", + "✨": "parıltılar", + "🎈": "balon", + "🎉": "parti konfetisi", + "🎏": "sazan şekilli flama", + "🎀": "kurdele", + "🎁": "paketlenmiş hediye", + "🎟️": "giriş bileti", + "🏆️": "zafer kupası", + "⚽️": "futbol topu", + "🏀": "basketbol", + "🏈": "amerikan futbolu", + "🎾": "tenis", + "🥏": "frizbi", + "🏓": "masa tenisi", + "🏸": "badminton", + "🤿": "dalgıç maskesi", + "🥌": "curling taşı", + "🎯": "tam isabet", + "🪀": "yoyo", + "🪁": "uçurtma", + "🔮": "kristal küre", + "🎲": "oyun zarı", + "🧩": "yapboz parçası", + "🎨": "boya paleti", + "🧵": "iplik", + "👕": "tişört", + "🧦": "çorap", + "👗": "elbise", + "🩳": "şort", + "🎒": "okul çantası", + "👟": "koşu ayakkabısı", + "👑": "taç", + "🧢": "siperli şapka", + "💄": "ruj", + "💍": "yüzük", + "💎": "mücevher", + "📢": "hoparlör", + "🎶": "müzik notaları", + "🎙️": "stüdyo mikrofonu", + "📻️": "radyo", + "🎷": "saksafon", + "🪗": "akordiyon", + "🎸": "gitar", + "🎺": "trompet", + "🎻": "keman", + "🪕": "banjo", + "🥁": "davul", + "☎️": "telefon", + "🔋": "pil", + "💿️": "optik disk", + "🧮": "abaküs", + "🎬️": "film tahtası", + "💡": "ampul", + "🔦": "el feneri", + "🏮": "kırmızı kağıt fener", + "📕": "kapalı kitap", + "🏷️": "etiket", + "💳️": "kredi kartı", + "✏️": "kurşun kalem", + "🖌️": "boya fırçası", + "🖍️": "pastel boya", + "📌": "raptiye", + "📎": "ataş", + "🔑": "anahtar", + "🪃": "bumerang", + "🏹": "ok ve yay", + "⚖️": "terazi", + "🧲": "mıknatıs", + "🧪": "deney tüpü", + "🧬": "dna", + "🔬": "mikroskop", + "🔭": "teleskop", + "📡": "uydu anteni", + "🪑": "sandalye", + "🧹": "süpürge", + "🗿": "moyai heykeli" + }, + "uk": { + "👽️": "прибулець", + "🤖": "робот", + "🧠": "мозок", + "👁️": "око", + "🧙": "маг", + "🧚": "фея", + "🧜": "казкова водяна істота", + "🐵": "морда мавпи", + "🦧": "орангутанг", + "🐶": "морда собаки", + "🐺": "вовк", + "🦊": "лис", + "🦝": "єнот", + "🐱": "морда кота", + "🦁": "лев", + "🐯": "морда тигра", + "🐴": "голова коня", + "🦄": "єдиноріг", + "🦓": "зебра", + "🦌": "олень", + "🐮": "морда корови", + "🐷": "рило свині", + "🐗": "вепр", + "🐪": "одногорбий верблюд", + "🦙": "лама", + "🦒": "жирафа", + "🐘": "слон", + "🦣": "мамонт", + "🦏": "носоріг", + "🐭": "морда миші", + "🐰": "морда кроля", + "🐿️": "бурундук", + "🦔": "їжак", + "🦇": "кажан", + "🐻": "ведмідь", + "🐨": "коала", + "🦥": "лінивець", + "🦦": "видра", + "🦘": "кенгуру", + "🐥": "курча, що стоїть", + "🐦️": "птах", + "🕊️": "голуб", + "🦆": "качка", + "🦉": "сова", + "🦤": "дронт", + "🪶": "пір’їна", + "🦩": "фламінго", + "🦚": "павич", + "🦜": "папуга", + "🐊": "крокодил", + "🐢": "черепаха", + "🦎": "ящірка", + "🐍": "змія", + "🐲": "голова дракона", + "🦕": "завропод", + "🐳": "кит, що пускає фонтан", + "🐬": "дельфін", + "🦭": "тюлень", + "🐟️": "риба", + "🐠": "тропічна риба", + "🦈": "акула", + "🐙": "восьминіг", + "🐚": "морська мушля", + "🐌": "равлик", + "🦋": "метелик", + "🐛": "комаха", + "🐝": "бджола", + "🐞": "сонечко", + "💐": "букет", + "🌹": "троянда", + "🌺": "гібіскус", + "🌻": "соняшник", + "🌷": "тюльпан", + "🌲": "вічнозелене дерево", + "🌳": "листяне дерево", + "🌴": "пальма", + "🌵": "кактус", + "🌿": "лікарська рослина", + "🍁": "кленовий листок", + "🍇": "виноград", + "🍈": "диня", + "🍉": "кавун", + "🍊": "танжерин", + "🍋": "лимон", + "🍌": "банан", + "🍍": "ананас", + "🥭": "манго", + "🍏": "зелене яблуко", + "🍐": "груша", + "🍑": "персик", + "🍒": "вишні", + "🍓": "полуниця", + "🫐": "лохина", + "🥝": "ківі", + "🍅": "томат", + "🫒": "оливка", + "🥥": "кокос", + "🥑": "авокадо", + "🍆": "баклажан", + "🥕": "морква", + "🌽": "качан кукурудзи", + "🌶️": "гострий перець", + "🥬": "листя салату", + "🥦": "броколі", + "🧅": "цибуля", + "🍄": "гриб", + "🥜": "арахіс", + "🥐": "круасан", + "🥖": "багет", + "🥨": "крендель", + "🥯": "бейгл", + "🥞": "млинці", + "🧇": "вафля", + "🍔": "гамбургер", + "🍕": "піца", + "🌭": "хот-дог", + "🌮": "тако", + "🍿": "попкорн", + "🦀": "краб", + "🦞": "омар", + "🍨": "морозиво", + "🍩": "пончик", + "🍪": "печиво", + "🎂": "торт на день народження", + "🧁": "капкейк", + "🍫": "плитка шоколаду", + "🍬": "цукерка", + "🍭": "льодяник", + "🫖": "чайник", + "🧃": "пакетик із напоєм", + "🧉": "мате", + "🧭": "компас", + "🏔️": "гора із засніженою верхівкою", + "🌋": "вулкан", + "🏕️": "кемпінг", + "🏝️": "безлюдний острів", + "🏡": "будинок із садом", + "⛲️": "фонтан", + "🎠": "коник на каруселі", + "🎡": "чортове колесо", + "🎢": "американські гірки", + "💈": "вивіска перукаря (смугастий стовп)", + "🚆": "потяг", + "🚋": "вагон трамвая", + "🚍️": "автобус, що наближається", + "🚕": "таксі", + "🚗": "автомобіль", + "🚚": "вантажівка для доставки", + "🚜": "трактор", + "🛵": "мопед", + "🛺": "авторикша", + "🛴": "самокат", + "🛹": "скейтборд", + "🛼": "роликовий ковзан", + "⚓️": "якір", + "⛵️": "вітрильник", + "🛶": "каное", + "🚤": "швидкохідний катер", + "🚢": "корабель", + "✈️": "літак", + "🚁": "гелікоптер", + "🚠": "канатна дорога в горах", + "🛰️": "супутник", + "🚀": "ракета", + "🛸": "летюча тарілка", + "⏰": "будильник", + "🌙": "серп місяця", + "🌡️": "термометр", + "☀️": "сонце", + "🪐": "планета з кільцем", + "🌟": "сяйна зірка", + "🌀": "циклон", + "🌈": "веселка", + "☂️": "парасолька", + "❄️": "сніжинка", + "☄️": "комета", + "🔥": "вогонь", + "💧": "крапля", + "🌊": "хвиля", + "🎃": "ліхтар-гарбуз", + "✨": "блискітки", + "🎈": "повітряна кулька", + "🎉": "хлопавка", + "🎏": "вітровказ у формі коропів", + "🎀": "бант зі стрічки", + "🎁": "подарунок", + "🎟️": "вхідні квитки", + "🏆️": "приз", + "⚽️": "футбольний м’яч", + "🏀": "баскетбольний м’яч", + "🏈": "мʼяч для американського футболу", + "🎾": "тенісний м’яч", + "🥏": "літаючий диск", + "🏓": "ракетка і кулька для пінг-понгу", + "🏸": "ракетка і волан для бадмінтону", + "🤿": "маска з трубкою", + "🥌": "камʼяна шайба для керлінгу", + "🎯": "мішень із прямим влученням", + "🪀": "йо-йо", + "🪁": "повітряний змій", + "🔮": "кришталева куля", + "🎲": "гральна кість", + "🧩": "елемент пазла", + "🎨": "палітра художника", + "🧵": "нитка", + "👕": "теніска поло", + "🧦": "шкарпетки", + "👗": "сукня", + "🩳": "шорти", + "🎒": "шкільний рюкзак", + "👟": "кросівка", + "👑": "корона", + "🧢": "бейсболка", + "💄": "помада", + "💍": "каблучка", + "💎": "коштовний камінь", + "📢": "гучномовець", + "🎶": "музичні ноти", + "🎙️": "студійний мікрофон", + "📻️": "радіоприймач", + "🎷": "саксофон", + "🪗": "акордеон", + "🎸": "гітара", + "🎺": "труба", + "🎻": "скрипка", + "🪕": "банджо", + "🥁": "барабан", + "☎️": "телефон", + "🔋": "батарея", + "💿️": "компакт-диск", + "🧮": "рахівниця", + "🎬️": "кінохлопавка", + "💡": "лампочка", + "🔦": "ліхтарик", + "🏮": "червоний паперовий ліхтар", + "📕": "закрита книга", + "🏷️": "бирка", + "💳️": "кредитна картка", + "✏️": "олівець", + "🖌️": "пензель", + "🖍️": "пастель", + "📌": "канцелярська кнопка", + "📎": "скріпка", + "🔑": "ключ", + "🪃": "бумеранг", + "🏹": "лук і стріла", + "⚖️": "ваги", + "🧲": "магніт", + "🧪": "пробірка", + "🧬": "ДНК", + "🔬": "мікроскоп", + "🔭": "телескоп", + "📡": "супутникова антена", + "🪑": "стілець", + "🧹": "мітла", + "🗿": "статуя з Острова Пасхи" + }, + "vi": { + "👽️": "người ngoài hành tinh", + "🤖": "mặt rô-bốt", + "🧠": "não", + "👁️": "mắt", + "🧙": "pháp sư", + "🧚": "tiên", + "🧜": "người cá", + "🐵": "mặt khỉ", + "🦧": "đười ươi", + "🐶": "mặt cún", + "🐺": "mặt chó sói", + "🦊": "mặt cáo", + "🦝": "gấu trúc", + "🐱": "mặt mèo", + "🦁": "mặt sư tử", + "🐯": "mặt hổ", + "🐴": "mặt ngựa", + "🦄": "mặt kỳ lân", + "🦓": "ngựa vằn", + "🦌": "hươu", + "🐮": "mặt bò", + "🐷": "mặt lợn", + "🐗": "lợn rừng", + "🐪": "lạc đà", + "🦙": "lạc đà không bướu", + "🦒": "hươu cao cổ", + "🐘": "voi", + "🦣": "voi ma mút", + "🦏": "tê giác", + "🐭": "mặt chuột", + "🐰": "mặt thỏ", + "🐿️": "sóc chuột", + "🦔": "nhím", + "🦇": "dơi", + "🐻": "mặt gấu", + "🐨": "gấu túi", + "🦥": "con lười", + "🦦": "rái cá", + "🦘": "chuột túi", + "🐥": "mặt trước gà con", + "🐦️": "chim", + "🕊️": "bồ câu", + "🦆": "vịt", + "🦉": "cú", + "🦤": "chim cưu", + "🪶": "lông chim", + "🦩": "hồng hạc", + "🦚": "con công", + "🦜": "con vẹt", + "🐊": "cá sấu", + "🐢": "rùa", + "🦎": "thằn lằn", + "🐍": "rắn", + "🐲": "mặt rồng", + "🦕": "khủng long chân thằn lằn", + "🐳": "cá voi đang phun nước", + "🐬": "cá heo", + "🦭": "chó biển", + "🐟️": "cá", + "🐠": "cá nhiệt đới", + "🦈": "cá mập", + "🐙": "bạch tuộc", + "🐚": "vỏ xoắn ốc", + "🐌": "ốc sên", + "🦋": "bướm", + "🐛": "con bọ", + "🐝": "ong mật", + "🐞": "bọ rùa", + "💐": "bó hoa", + "🌹": "hoa hồng", + "🌺": "hoa dâm bụt", + "🌻": "hoa hướng dương", + "🌷": "hoa tulip", + "🌲": "cây thường xanh", + "🌳": "cây rụng lá", + "🌴": "cây cọ", + "🌵": "cây xương rồng", + "🌿": "thảo mộc", + "🍁": "lá phong", + "🍇": "chùm nho", + "🍈": "dưa", + "🍉": "dưa hấu", + "🍊": "quýt", + "🍋": "chanh", + "🍌": "chuối", + "🍍": "dứa", + "🥭": "xoài", + "🍏": "táo xanh", + "🍐": "lê", + "🍑": "đào", + "🍒": "anh đào", + "🍓": "dâu tây", + "🫐": "quả việt quất", + "🥝": "quả kiwi", + "🍅": "cà chua", + "🫒": "ôliu", + "🥥": "dừa", + "🥑": "quả bơ", + "🍆": "cà tím", + "🥕": "cà rốt", + "🌽": "bắp ngô", + "🌶️": "quả ớt", + "🥬": "xanh lá", + "🥦": "xúp lơ xanh", + "🧅": "hành", + "🍄": "nấm", + "🥜": "đậu phộng", + "🥐": "bánh sừng bò", + "🥖": "bánh mì que", + "🥨": "bánh quy xoắn", + "🥯": "bánh mỳ vòng", + "🥞": "bánh kếp", + "🧇": "bánh quế", + "🍔": "bánh hamburger", + "🍕": "bánh pizza", + "🌭": "bánh mì xúc xích", + "🌮": "bánh taco", + "🍿": "bỏng ngô", + "🦀": "cua", + "🦞": "tôm hùm", + "🍨": "kem", + "🍩": "bánh rán vòng", + "🍪": "bánh quy", + "🎂": "bánh sinh nhật", + "🧁": "bánh nướng nhỏ", + "🍫": "thanh sô cô la", + "🍬": "kẹo", + "🍭": "kẹo mút", + "🫖": "ấm trà", + "🧃": "hộp đồ uống", + "🧉": "trà nhựa ruồi", + "🧭": "la bàn", + "🏔️": "đỉnh núi phủ tuyết", + "🌋": "núi lửa", + "🏕️": "cắm trại", + "🏝️": "đảo hoang", + "🏡": "nhà có vườn", + "⛲️": "đài phun nước", + "🎠": "ngựa đu quay", + "🎡": "vòng đu quay", + "🎢": "tàu lượn siêu tốc", + "💈": "biển hiệu của thợ cắt tóc", + "🚆": "tàu hỏa", + "🚋": "tàu điện", + "🚍️": "xe buýt đang tới", + "🚕": "taxi", + "🚗": "ô tô", + "🚚": "xe tải giao hàng", + "🚜": "máy kéo", + "🛵": "xe tay ga", + "🛺": "xe lam", + "🛴": "xe hẩy", + "🛹": "ván trượt", + "🛼": "giày trượt patin", + "⚓️": "mỏ neo", + "⛵️": "thuyền buồm", + "🛶": "xuồng", + "🚤": "xuồng cao tốc", + "🚢": "tàu thủy", + "✈️": "máy bay", + "🚁": "máy bay trực thăng", + "🚠": "cáp treo trên núi", + "🛰️": "vệ tinh", + "🚀": "tên lửa", + "🛸": "đĩa bay", + "⏰": "đồng hồ báo thức", + "🌙": "trăng lưỡi liềm", + "🌡️": "nhiệt kế", + "☀️": "mặt trời", + "🪐": "hành tinh có vành đai bao quanh", + "🌟": "ngôi sao lấp lánh", + "🌀": "hình lốc xoáy", + "🌈": "cầu vồng", + "☂️": "cái ô", + "❄️": "bông tuyết", + "☄️": "sao chổi", + "🔥": "lửa", + "💧": "giọt nước", + "🌊": "sóng nước", + "🎃": "đèn lồng bí ngô", + "✨": "ánh lấp lánh", + "🎈": "bóng bay", + "🎉": "pháo giấy buổi tiệc", + "🎏": "cờ cá chép", + "🎀": "ruy băng", + "🎁": "gói quà", + "🎟️": "vé vào cửa", + "🏆️": "cúp", + "⚽️": "bóng đá", + "🏀": "bóng rổ", + "🏈": "bóng bầu dục Mỹ", + "🎾": "quần vợt", + "🥏": "đĩa bay trò chơi", + "🏓": "bóng bàn", + "🏸": "cầu lông", + "🤿": "mặt nạ lặn", + "🥌": "bi đá trên băng", + "🎯": "trúng đích", + "🪀": "yo-yo", + "🪁": "diều", + "🔮": "quả cầu pha lê", + "🎲": "trò xúc xắc", + "🧩": "ghép hình", + "🎨": "bảng màu", + "🧵": "sợi chỉ", + "👕": "áo phông", + "🧦": "tất", + "👗": "váy", + "🩳": "quần soóc", + "🎒": "ba lô đi học", + "👟": "giày chạy", + "👑": "vương miện", + "🧢": "mũ lưỡi trai", + "💄": "son môi", + "💍": "nhẫn", + "💎": "đá quý", + "📢": "loa phát thanh", + "🎶": "các nốt nhạc", + "🎙️": "micrô phòng thu âm", + "📻️": "radio", + "🎷": "kèn saxophone", + "🪗": "phong cầm", + "🎸": "đàn ghi-ta", + "🎺": "kèn trumpet", + "🎻": "đàn violin", + "🪕": "đàn banjo", + "🥁": "trống", + "☎️": "điện thoại bàn", + "🔋": "pin", + "💿️": "đĩa quang", + "🧮": "bàn tính", + "🎬️": "bảng clapper", + "💡": "bóng đèn", + "🔦": "đèn pin", + "🏮": "đèn lồng giấy màu đỏ", + "📕": "sách đóng", + "🏷️": "nhãn", + "💳️": "thẻ tín dụng", + "✏️": "bút chì", + "🖌️": "cọ vẽ tranh", + "🖍️": "bút sáp màu", + "📌": "đinh ghim", + "📎": "kẹp giấy", + "🔑": "chìa khóa", + "🪃": "bumơrang", + "🏹": "cung tên", + "⚖️": "cân thăng bằng", + "🧲": "nam châm", + "🧪": "ống nghiệm", + "🧬": "adn", + "🔬": "kính hiển vi", + "🔭": "kính viễn vọng", + "📡": "ăng-ten vệ tinh", + "🪑": "ghế", + "🧹": "cây chổi", + "🗿": "tượng moai" + }, + "zh-CN": { + "👽️": "外星人", + "🤖": "机器人", + "🧠": "脑", + "👁️": "眼睛", + "🧙": "法师", + "🧚": "精灵", + "🧜": "人鱼", + "🐵": "猴头", + "🦧": "红毛猩猩", + "🐶": "狗脸", + "🐺": "狼", + "🦊": "狐狸", + "🦝": "浣熊", + "🐱": "猫脸", + "🦁": "狮子", + "🐯": "老虎头", + "🐴": "马头", + "🦄": "独角兽", + "🦓": "斑马", + "🦌": "鹿", + "🐮": "奶牛头", + "🐷": "猪头", + "🐗": "野猪", + "🐪": "骆驼", + "🦙": "美洲鸵", + "🦒": "长颈鹿", + "🐘": "大象", + "🦣": "猛犸", + "🦏": "犀牛", + "🐭": "老鼠头", + "🐰": "兔子头", + "🐿️": "松鼠", + "🦔": "刺猬", + "🦇": "蝙蝠", + "🐻": "熊", + "🐨": "考拉", + "🦥": "树懒", + "🦦": "水獭", + "🦘": "袋鼠", + "🐥": "正面朝向的小鸡", + "🐦️": "鸟", + "🕊️": "鸽", + "🦆": "鸭子", + "🦉": "猫头鹰", + "🦤": "渡渡鸟", + "🪶": "羽毛", + "🦩": "火烈鸟", + "🦚": "孔雀", + "🦜": "鹦鹉", + "🐊": "鳄鱼", + "🐢": "龟", + "🦎": "蜥蜴", + "🐍": "蛇", + "🐲": "龙头", + "🦕": "蜥蜴类", + "🐳": "喷水的鲸", + "🐬": "海豚", + "🦭": "海豹", + "🐟️": "鱼", + "🐠": "热带鱼", + "🦈": "鲨鱼", + "🐙": "章鱼", + "🐚": "海螺", + "🐌": "蜗牛", + "🦋": "蝴蝶", + "🐛": "毛毛虫", + "🐝": "蜜蜂", + "🐞": "瓢虫", + "💐": "花束", + "🌹": "玫瑰", + "🌺": "芙蓉", + "🌻": "向日葵", + "🌷": "郁金香", + "🌲": "松树", + "🌳": "落叶树", + "🌴": "棕榈树", + "🌵": "仙人掌", + "🌿": "药草", + "🍁": "枫叶", + "🍇": "葡萄", + "🍈": "甜瓜", + "🍉": "西瓜", + "🍊": "橘子", + "🍋": "柠檬", + "🍌": "香蕉", + "🍍": "菠萝", + "🥭": "芒果", + "🍏": "青苹果", + "🍐": "梨", + "🍑": "桃", + "🍒": "樱桃", + "🍓": "草莓", + "🫐": "蓝莓", + "🥝": "猕猴桃", + "🍅": "西红柿", + "🫒": "橄榄", + "🥥": "椰子", + "🥑": "鳄梨", + "🍆": "茄子", + "🥕": "胡萝卜", + "🌽": "玉米", + "🌶️": "红辣椒", + "🥬": "绿叶蔬菜", + "🥦": "西兰花", + "🧅": "洋葱", + "🍄": "蘑菇", + "🥜": "花生", + "🥐": "羊角面包", + "🥖": "法式长棍面包", + "🥨": "椒盐卷饼", + "🥯": "面包圈", + "🥞": "烙饼", + "🧇": "华夫饼", + "🍔": "汉堡", + "🍕": "披萨", + "🌭": "热狗", + "🌮": "墨西哥卷饼", + "🍿": "爆米花", + "🦀": "蟹", + "🦞": "龙虾", + "🍨": "冰淇淋", + "🍩": "甜甜圈", + "🍪": "饼干", + "🎂": "生日蛋糕", + "🧁": "纸杯蛋糕", + "🍫": "巧克力", + "🍬": "糖", + "🍭": "棒棒糖", + "🫖": "茶壶", + "🧃": "饮料盒", + "🧉": "马黛茶", + "🧭": "指南针", + "🏔️": "雪山", + "🌋": "火山", + "🏕️": "露营", + "🏝️": "无人荒岛", + "🏡": "别墅", + "⛲️": "喷泉", + "🎠": "旋转木马", + "🎡": "摩天轮", + "🎢": "过山车", + "💈": "理发店", + "🚆": "火车", + "🚋": "有轨电车", + "🚍️": "迎面驶来的公交车", + "🚕": "出租车", + "🚗": "汽车", + "🚚": "货车", + "🚜": "拖拉机", + "🛵": "小型摩托车", + "🛺": "三轮摩托车", + "🛴": "滑板车", + "🛹": "滑板", + "🛼": "四轮滑冰鞋", + "⚓️": "锚", + "⛵️": "帆船", + "🛶": "独木舟", + "🚤": "快艇", + "🚢": "船", + "✈️": "飞机", + "🚁": "直升机", + "🚠": "缆车", + "🛰️": "卫星", + "🚀": "火箭", + "🛸": "飞碟", + "⏰": "闹钟", + "🌙": "弯月", + "🌡️": "温度计", + "☀️": "太阳", + "🪐": "有环行星", + "🌟": "闪亮的星星", + "🌀": "台风", + "🌈": "彩虹", + "☂️": "伞", + "❄️": "雪花", + "☄️": "彗星", + "🔥": "火焰", + "💧": "水滴", + "🌊": "浪花", + "🎃": "南瓜灯", + "✨": "闪亮", + "🎈": "气球", + "🎉": "拉炮彩带", + "🎏": "鲤鱼旗", + "🎀": "蝴蝶结", + "🎁": "礼物", + "🎟️": "入场券", + "🏆️": "奖杯", + "⚽️": "足球", + "🏀": "篮球", + "🏈": "美式橄榄球", + "🎾": "网球", + "🥏": "飞盘", + "🏓": "乒乓球", + "🏸": "羽毛球", + "🤿": "潜水面罩", + "🥌": "冰壶", + "🎯": "正中靶心的飞镖", + "🪀": "悠悠球", + "🪁": "风筝", + "🔮": "水晶球", + "🎲": "骰子", + "🧩": "拼图", + "🎨": "调色盘", + "🧵": "线", + "👕": "T恤", + "🧦": "袜子", + "👗": "连衣裙", + "🩳": "短裤", + "🎒": "书包", + "👟": "跑鞋", + "👑": "皇冠", + "🧢": "鸭舌帽", + "💄": "唇膏", + "💍": "戒指", + "💎": "宝石", + "📢": "喇叭", + "🎶": "多个音符", + "🎙️": "录音室麦克风", + "📻️": "收音机", + "🎷": "萨克斯管", + "🪗": "手风琴", + "🎸": "吉他", + "🎺": "小号", + "🎻": "小提琴", + "🪕": "班卓琴", + "🥁": "鼓", + "☎️": "电话", + "🔋": "电池", + "💿️": "光盘", + "🧮": "算盘", + "🎬️": "场记板", + "💡": "灯泡", + "🔦": "手电筒", + "🏮": "红灯笼", + "📕": "合上的书本", + "🏷️": "标签", + "💳️": "信用卡", + "✏️": "铅笔", + "🖌️": "画笔", + "🖍️": "蜡笔", + "📌": "图钉", + "📎": "回形针", + "🔑": "钥匙", + "🪃": "回旋镖", + "🏹": "弓和箭", + "⚖️": "天平", + "🧲": "磁铁", + "🧪": "试管", + "🧬": "DNA", + "🔬": "显微镜", + "🔭": "望远镜", + "📡": "卫星天线", + "🪑": "椅子", + "🧹": "扫帚", + "🗿": "摩埃" + }, + "zh-TW": { + "👽️": "外星人", + "🤖": "機器人", + "🧠": "腦", + "👁️": "眼睛", + "🧙": "魔術師", + "🧚": "仙女", + "🧜": "人魚", + "🐵": "猴子頭", + "🦧": "猩猩", + "🐶": "狗頭", + "🐺": "狼", + "🦊": "狐狸", + "🦝": "浣熊", + "🐱": "貓頭", + "🦁": "獅子", + "🐯": "老虎頭", + "🐴": "馬頭", + "🦄": "獨角獸", + "🦓": "斑馬", + "🦌": "鹿", + "🐮": "牛頭", + "🐷": "豬頭", + "🐗": "野豬", + "🐪": "單峰駱駝", + "🦙": "羊駝", + "🦒": "長頸鹿", + "🐘": "大象", + "🦣": "毛象", + "🦏": "犀牛", + "🐭": "老鼠頭", + "🐰": "兔子頭", + "🐿️": "松鼠", + "🦔": "刺蝟", + "🦇": "蝙蝠", + "🐻": "熊", + "🐨": "無尾熊", + "🦥": "樹懶", + "🦦": "水獺", + "🦘": "袋鼠", + "🐥": "小雞", + "🐦️": "鳥", + "🕊️": "飛鳥", + "🦆": "鴨子", + "🦉": "貓頭鷹", + "🦤": "渡渡鳥", + "🪶": "羽毛", + "🦩": "紅鶴", + "🦚": "孔雀", + "🦜": "鸚鵡", + "🐊": "鱷魚", + "🐢": "烏龜", + "🦎": "蜥蜴", + "🐍": "蛇", + "🐲": "龍頭", + "🦕": "蜥腳類恐龍", + "🐳": "鯨魚", + "🐬": "海豚", + "🦭": "海豹", + "🐟️": "魚", + "🐠": "熱帶魚", + "🦈": "鯊魚", + "🐙": "章魚", + "🐚": "海螺", + "🐌": "蝸牛", + "🦋": "蝴蝶", + "🐛": "毛毛蟲", + "🐝": "蜜蜂", + "🐞": "瓢蟲", + "💐": "花束", + "🌹": "玫瑰", + "🌺": "芙蓉", + "🌻": "向日葵", + "🌷": "鬱金香", + "🌲": "常青樹", + "🌳": "落葉樹", + "🌴": "棕櫚樹", + "🌵": "仙人掌", + "🌿": "草藥", + "🍁": "楓葉", + "🍇": "葡萄", + "🍈": "瓜", + "🍉": "西瓜", + "🍊": "橘子", + "🍋": "檸檬", + "🍌": "香蕉", + "🍍": "鳳梨", + "🥭": "芒果", + "🍏": "青蘋果", + "🍐": "梨子", + "🍑": "桃子", + "🍒": "櫻桃", + "🍓": "草莓", + "🫐": "藍莓", + "🥝": "奇異果", + "🍅": "番茄", + "🫒": "橄欖", + "🥥": "椰子", + "🥑": "酪梨", + "🍆": "茄子", + "🥕": "胡蘿蔔", + "🌽": "玉米", + "🌶️": "辣椒", + "🥬": "綠葉蔬菜", + "🥦": "花椰菜", + "🧅": "洋蔥", + "🍄": "蘑菇", + "🥜": "花生", + "🥐": "可頌", + "🥖": "法國麵包", + "🥨": "蝴蝶餅", + "🥯": "貝果", + "🥞": "鬆餅", + "🧇": "格子鬆餅", + "🍔": "漢堡", + "🍕": "披薩", + "🌭": "熱狗", + "🌮": "夾餅", + "🍿": "爆米花", + "🦀": "螃蟹", + "🦞": "龍蝦", + "🍨": "冰淇淋", + "🍩": "甜甜圈", + "🍪": "餅乾", + "🎂": "生日蛋糕", + "🧁": "杯子蛋糕", + "🍫": "巧克力", + "🍬": "糖", + "🍭": "棒棒糖", + "🫖": "茶壺", + "🧃": "鋁箔包", + "🧉": "瑪黛茶", + "🧭": "指南針", + "🏔️": "雪山", + "🌋": "火山", + "🏕️": "露營", + "🏝️": "熱帶小島", + "🏡": "別墅", + "⛲️": "噴泉", + "🎠": "旋轉木馬", + "🎡": "摩天輪", + "🎢": "雲霄飛車", + "💈": "理髮店", + "🚆": "火車", + "🚋": "電纜車", + "🚍️": "公共汽車", + "🚕": "計程車", + "🚗": "汽車", + "🚚": "貨車", + "🚜": "拖弋機", + "🛵": "摩托車", + "🛺": "嘟嘟車", + "🛴": "滑板車", + "🛹": "滑板", + "🛼": "輪式溜冰鞋", + "⚓️": "錨", + "⛵️": "帆船", + "🛶": "獨木舟", + "🚤": "快艇", + "🚢": "船", + "✈️": "飛機", + "🚁": "直升機", + "🚠": "纜車", + "🛰️": "衛星", + "🚀": "火箭", + "🛸": "飛碟", + "⏰": "鬧鐘", + "🌙": "彎月", + "🌡️": "溫度計", + "☀️": "太陽", + "🪐": "帶行星環的行星", + "🌟": "閃爍的星星", + "🌀": "颱風", + "🌈": "彩虹", + "☂️": "雨傘", + "❄️": "雪花", + "☄️": "慧星", + "🔥": "火", + "💧": "水滴", + "🌊": "波浪", + "🎃": "南瓜燈", + "✨": "閃爍", + "🎈": "氣球", + "🎉": "拉炮", + "🎏": "鯉魚旗", + "🎀": "蝴蝶結", + "🎁": "禮物", + "🎟️": "入場券", + "🏆️": "獎盃", + "⚽️": "足球", + "🏀": "籃球", + "🏈": "美式足球", + "🎾": "網球", + "🥏": "飛盤", + "🏓": "桌球", + "🏸": "羽毛球", + "🤿": "潛水面罩", + "🥌": "冰石壺", + "🎯": "命中", + "🪀": "溜溜球", + "🪁": "風箏", + "🔮": "水晶球", + "🎲": "骰子", + "🧩": "拼圖", + "🎨": "調色板", + "🧵": "線", + "👕": "T卹", + "🧦": "襪子", + "👗": "洋裝", + "🩳": "短泳褲", + "🎒": "書包", + "👟": "運動鞋", + "👑": "皇冠", + "🧢": "鴨舌帽", + "💄": "口紅", + "💍": "戒指", + "💎": "鑽石", + "📢": "大聲公", + "🎶": "樂符", + "🎙️": "錄音室麥克風", + "📻️": "收音機", + "🎷": "薩克斯風", + "🪗": "手風琴", + "🎸": "吉他", + "🎺": "小號", + "🎻": "小提琴", + "🪕": "斑鳩琴", + "🥁": "鼓", + "☎️": "電話", + "🔋": "電池", + "💿️": "光碟", + "🧮": "算盤", + "🎬️": "場記板", + "💡": "燈泡", + "🔦": "手電筒", + "🏮": "燈籠", + "📕": "合起來的書本", + "🏷️": "吊牌", + "💳️": "信用卡", + "✏️": "鉛筆", + "🖌️": "畫筆", + "🖍️": "蠟筆", + "📌": "圖釘", + "📎": "迴紋針", + "🔑": "鑰匙", + "🪃": "迴力鏢", + "🏹": "弓箭", + "⚖️": "天平", + "🧲": "磁鐵", + "🧪": "試管", + "🧬": "DNA", + "🔬": "顯微鏡", + "🔭": "望遠鏡", + "📡": "衛星天線", + "🪑": "椅子", + "🧹": "掃帚", + "🗿": "復活節島" + } +} \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f300.svg b/browser/components/torpreferences/content/bridgemoji/1f300.svg new file mode 100644 index 000000000000..1de6f256cb52 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f300.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M35.782 24.518c-.13-.438-.422-.799-.821-1.016-.802-.436-1.879-.116-2.316.683-1.797 3.296-4.771 5.695-8.372 6.757-3.563 1.051-7.437.634-10.698-1.144-2.558-1.394-4.419-3.699-5.242-6.493-.74-2.514-.495-5.016.552-7.033-.363 1.605-.313 3.285.164 4.908.737 2.507 2.407 4.575 4.701 5.823 2.733 1.492 5.989 1.841 8.979.961 3.025-.892 5.521-2.906 7.026-5.672 1.832-3.358 2.246-7.228 1.165-10.898-1.08-3.669-3.524-6.69 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f308.svg b/browser/components/torpreferences/content/bridgemoji/1f308.svg new file mode 100644 index 000000000000..ffe6a12398cf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f308.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8767AC" d="M36 1C16.118 1 1 16.118 1 36h17.042c0-9.917 8.042-17.958 17.958-17.958V1z"/><path fill="#EB2027" d="M0 35.999h3.042c0-18.189 14.734-32.935 32.917-32.957V0C16.095.023 0 16.131 0 35.999z"/><path fill="#F19020" d="M3.083 36h3C6.083 19.468 19.473 6.065 36 6.043v-3C17.817 3.065 3.083 17.811 3.083 36z"/><path fill="#FFCB4C" d="M6.083 36h3C9.083 21.125 21.13 9.065 36 9.043v-3C19.473 6.065 6.083 19.468 6.083 36z" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f30a.svg b/browser/components/torpreferences/content/bridgemoji/1f30a.svg new file mode 100644 index 000000000000..0e68ec3614f8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f30a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#269" d="M33.398 23.678c-7.562 4.875-20.062-.438-18.375-8.062 1.479-6.684 9.419-4.763 11.225-3.861 1.806.902.713-3.889-3.475-5.327C17.1 4.48 10.156 4.893 7.961 14.678c-1.5 6.687 1.438 16.062 12.719 16.187 11.281.125 12.718-7.187 12.718-7.187z"/><path fill="#55ACEE" d="M35.988 25.193c0-2.146-2.754-2.334-4-1.119-2.994 2.919-7.402 4.012-13.298 2.861-10.25-2-10.341-14.014-3.333-17.441 3.791-1.854 8.289.341 9.999 1.655 1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f30b.svg b/browser/components/torpreferences/content/bridgemoji/1f30b.svg new file mode 100644 index 000000000000..88d989d73c53 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f30b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M12 36V16s-1.17 1.053-2.354 1.929c-.313.231-1.977.79-2.312 1.04-1.762 1.315-2.552 3.841-2.792 4.167C4.083 23.76 0 36 0 36h12z"/><path fill="#4B545D" d="M32 36l-5.5-17.75-8.5-5.656-6.458 2.208C11.135 15.583 10 18.25 10 18.25L8.75 22 0 36h32z"/><path fill="#292F33" d="M18.135 25.726c-.145-.513-.026-.97-.177-1.476-.148-.499-.566-1.047-.693-1.478-.265-.899-.454-1.583-.445-1.772.024-.469.894-.401 1.102.469.293 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f319.svg b/browser/components/torpreferences/content/bridgemoji/1f319.svg new file mode 100644 index 000000000000..d98dc2f9f42f --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f319.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M30.312.776C32 19 20 32 .776 30.312c8.199 7.717 21.091 7.588 29.107-.429C37.9 21.867 38.03 8.975 30.312.776z"/><path d="M30.705 15.915c-.453.454-.453 1.189 0 1.644.454.453 1.189.453 1.643 0 .454-.455.455-1.19 0-1.644-.453-.454-1.189-.454-1.643 0zm-16.022 14.38c-.682.681-.682 1.783 0 2.465.68.682 1.784.682 2.464 0 .681-.682.681-1.784 0-2.465-.68-.682-1.784-.682-2.464 0zm13.968-2.147c-1.135 1.135-2.974 1.13 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f31f.svg b/browser/components/torpreferences/content/bridgemoji/1f31f.svg new file mode 100644 index 000000000000..a4695dd6df57 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f31f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M28.84 17.638c-.987 1.044-1.633 3.067-1.438 4.493l.892 6.441c.197 1.427-.701 2.087-1.996 1.469l-5.851-2.796c-1.295-.62-3.408-.611-4.7.018l-5.826 2.842c-1.291.629-2.193-.026-2.007-1.452l.843-6.449c.186-1.427-.475-3.444-1.47-4.481l-4.494-4.688c-.996-1.037-.655-2.102.755-2.365l6.37-1.188c1.41-.263 3.116-1.518 3.793-2.789L16.762.956c.675-1.271 1.789-1.274 2.473-.009L22.33 6.66c.686 1.265 2.4 2.507 3.814 2.758 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f321.svg b/browser/components/torpreferences/content/bridgemoji/1f321.svg new file mode 100644 index 000000000000..95a75984e1ff --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f321.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M33.536 2.464c-1.953-1.953-5.118-1.953-7.071 0l-1.197 1.199 2.842 2.843c.391.391.391 1.024 0 1.414-.195.195-.451.293-.707.293s-.512-.098-.707-.293l-2.841-2.842-2.11 2.112 2.841 2.84c.391.39.391 1.023 0 1.414-.195.195-.451.293-.707.293s-.512-.098-.707-.293l-2.84-2.839-2.12 2.122 2.837 2.838c.391.391.391 1.024 0 1.414-.195.195-.451.293-.707.293s-.512-.098-.707-.293l-2.837-2.837-2.12 2.123 2.836 2.836c.391.3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f32d.svg b/browser/components/torpreferences/content/bridgemoji/1f32d.svg new file mode 100644 index 000000000000..a450dbba085e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f32d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M31.42 31.471c-3.515 3.515-9.213 3.515-12.728 0L4.55 17.328c-3.515-3.515-3.515-9.213 0-12.728 3.515-3.515 9.213-3.515 12.728 0L31.42 18.743c3.515 3.514 3.515 9.213 0 12.728z"/><path fill="#F7BBA6" d="M29.335 20.9c3.515 3.515 4.609 8.119 2.475 10.253-2.135 2.134-6.739 1.039-10.253-2.475L7.414 14.536c-3.515-3.515-4.609-8.12-2.475-10.253 2.134-2.134 6.738-1.04 10.253 2.475L29.335 20.9z"/><path fill="#DD2E44" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f32e.svg b/browser/components/torpreferences/content/bridgemoji/1f32e.svg new file mode 100644 index 000000000000..5b08f1f7d199 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f32e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFE8B6" d="M12 7C5.374 7 0 14.164 0 23h6v6h17.119c.564-1.854.881-3.877.881-6 0-8.836-5.373-16-12-16z"/><path fill="#FFE8B6" d="M29 21h4.896C33.156 13.11 28.118 7 22 7c-6.627 0-12 7.164-12 16 0 2.123.317 4.146.88 6H29v-8z"/><path fill="#FFAC33" d="M36 23c0-8.836-5.373-16-12-16-6.626 0-12 7.164-12 16 0 2.123.317 4.146.88 6H30c3.314 0 6-2.685 6-6z"/><path fill="#FFAC33" d="M6 23h10v6H6z"/><path fill="#FFE8B6" d="M0 23c [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f332.svg b/browser/components/torpreferences/content/bridgemoji/1f332.svg new file mode 100644 index 000000000000..540f1860cc16 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f332.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M22 33c0 2.209-1.791 3-4 3s-4-.791-4-3l1-9c0-2.209.791-2 3-2s3-.209 3 2l1 9z"/><path fill="#5C913B" d="M31.406 27.297C24.443 21.332 21.623 12.791 18 12.791c-3.623 0-6.443 8.541-13.405 14.506-2.926 2.507-1.532 3.957 2.479 3.667 3.576-.258 6.919-1.069 10.926-1.069s7.352.812 10.926 1.069c4.012.29 5.405-1.16 2.48-3.667z"/><path fill="#3E721D" d="M29.145 24.934C23.794 20.027 20.787 13 18 13c-2.785 0-5.793 7.02 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f333.svg b/browser/components/torpreferences/content/bridgemoji/1f333.svg new file mode 100644 index 000000000000..3937fc499f8b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f333.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M22 33c0 2.209-1.791 3-4 3s-4-.791-4-3l1-9c0-2.209.791-2 3-2s3-.209 3 2l1 9z"/><path fill="#5C913B" d="M34 17c0 8.837-7.163 12-16 12-8.836 0-16-3.163-16-12C2 8.164 11 0 18 0s16 8.164 16 17z"/><g fill="#3E721D"><ellipse cx="6" cy="21" rx="2" ry="1"/><ellipse cx="30" cy="21" rx="2" ry="1"/><ellipse cx="10" cy="25" rx="2" ry="1"/><ellipse cx="14" cy="22" rx="2" ry="1"/><ellipse cx="10" cy="16" rx="2" ry="1"/ [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f334.svg b/browser/components/torpreferences/content/bridgemoji/1f334.svg new file mode 100644 index 000000000000..55d246a2fe7a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f334.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M21.978 20.424c-.054-.804-.137-1.582-.247-2.325-.133-.89-.299-1.728-.485-2.513-.171-.723-.356-1.397-.548-2.017-.288-.931-.584-1.738-.852-2.4-.527-1.299-.943-2.043-.943-2.043l-3.613.466s.417.87.868 2.575c.183.692.371 1.524.54 2.495.086.49.166 1.012.238 1.573.1.781.183 1.632.242 2.549.034.518.058 1.058.074 1.619.006.204.015.401.018.611.01.656-.036 1.323-.118 1.989-.074.6-.182 1.197-.311 1.789-.185.848-.413 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f335.svg b/browser/components/torpreferences/content/bridgemoji/1f335.svg new file mode 100644 index 000000000000..097dc13c440e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f335.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M30 4c-2.209 0-4 1.791-4 4v9.125c0 1.086-.887 1.96-2 2.448V6c0-3.313-2.687-6-6-6s-6 2.687-6 6v17.629c-1.122-.475-2-1.371-2-2.504V16c0-2.209-1.791-4-4-4s-4 1.791-4 4v7c0 2.209 1.75 3.875 3.375 4.812 1.244.718 4.731 1.6 6.625 1.651V33c0 3.313 12 3.313 12 0v-7.549c1.981-.119 5.291-.953 6.479-1.639C32.104 22.875 34 21.209 34 19V8c0-2.209-1.791-4-4-4z"/><g fill="#3E721D"><circle cx="12" cy="6" r="1"/><circle c [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f336.svg b/browser/components/torpreferences/content/bridgemoji/1f336.svg new file mode 100644 index 000000000000..eaeef864dc48 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f336.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DA2F47" d="M4.042 27.916c4.89.551 9.458-1.625 13.471-5.946 4.812-5.182 5-13 5-14s11.31-3.056 11 5c-.43 11.196-7.43 20.946-19.917 21.916-5.982.465-9.679-.928-11.387-2.345-2.69-2.231-.751-4.916 1.833-4.625z"/><path fill="#77B255" d="M30.545 6.246c.204-1.644.079-3.754-.747-4.853-1.111-1.479-4.431-.765-3.569.113.96.979 2.455 2.254 2.401 4.151-.044-.01-.085-.022-.13-.032-3.856-.869-6.721 1.405-7.167 2.958-.782 2.722 4.06 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f337.svg b/browser/components/torpreferences/content/bridgemoji/1f337.svg new file mode 100644 index 000000000000..86a1a36f99db --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f337.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M34.751 22c-3.382 0-11.9 3.549-15.751 7.158V17c0-.553-.447-1-1-1-.552 0-1 .447-1 1v12.341C13.247 25.669 4.491 22 1.052 22 .123 22 11.913 35.992 17 34.599V35c0 .553.448 1 1 1 .553 0 1-.447 1-1v-.356C24.188 35.638 35.668 22 34.751 22z"/><path fill="#EA596E" d="M25 13.417C25 19.768 23.293 23 18 23s-7-3.232-7-9.583S16 0 18 0s7 7.066 7 13.417z"/><path fill="#F4ABBA" d="M22.795 2c-.48 0-4.106 14.271-4.803 19.27 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f339.svg b/browser/components/torpreferences/content/bridgemoji/1f339.svg new file mode 100644 index 000000000000..500d9257cece --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f339.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M19.32 25.358c-.113 0-.217.003-.32.005v-.415c4.805-.479 8.548-4.264 8.548-7.301 0-3.249 0 1.47-9.562 1.47-9.558 0-9.558-4.719-9.558-1.47 0 3.043 3.757 6.838 8.572 7.305v.411c-.104-.002-.207-.005-.321-.005-2.553 0-6.603-2.05-6.603-1.32 0 .646 4.187 4.017 6.924 4.796V35c0 .553.447 1 1 1s1-.447 1-1v-6.166c2.738-.779 6.924-4.15 6.924-4.796 0-.729-4.05 1.32-6.604 1.32z"/><path fill="#A0041E" d="M26.527 7.353c- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f33a.svg b/browser/components/torpreferences/content/bridgemoji/1f33a.svg new file mode 100644 index 000000000000..19c2f896020e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f33a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M19.602 32.329c6.509 6.506 17.254-7.669 15.72-7.669-7.669 0-22.227 1.161-15.72 7.669z"/><path fill="#77B255" d="M15.644 33.372C9.612 39.404-.07 26.263 1.352 26.263c3.81 0 9.374-.348 12.79.867 2.958 1.052 4.304 3.442 1.502 6.242z"/><path fill="#F4ABBA" d="M34.613 15.754c-.052-.901-.175-2.585-1.398-4.227-1.16-1.549-3.805-3.371-5.534-2.585.516-1.676-.264-4.125-1.191-5.49-1.179-1.736-4.262-3.843-8.146-3.026-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f33b.svg b/browser/components/torpreferences/content/bridgemoji/1f33b.svg new file mode 100644 index 000000000000..413e6fcbf87b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f33b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M28 27c-8 0-8 6-8 6V22h-4v11s0-6-8-6c-4 0-7-2-7-2s0 9 9 9h6s0 2 2 2 2-2 2-2h6c9 0 9-9 9-9s-3 2-7 2z"/><path fill="#FFAC33" d="M21.125 27.662c-.328 0-.651-.097-.927-.283l-2.323-1.575-2.322 1.575c-.277.186-.601.283-.929.283-.143 0-.287-.018-.429-.057-.462-.123-.851-.441-1.06-.874l-1.225-2.527-2.797.204c-.04.002-.079.004-.119.004-.438 0-.86-.174-1.17-.484-.34-.342-.516-.81-.481-1.288l.201-2.8-2.523-1.225c-.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f33d.svg b/browser/components/torpreferences/content/bridgemoji/1f33d.svg new file mode 100644 index 000000000000..6c4ae3bf53b0 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f33d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M15.373 1.022C13.71 2.686 8.718 9.34 11.214 15.164c2.495 5.823 5.909 2.239 7.486-2.495.832-2.496.832-5.824-.831-10.815-.832-2.496-2.496-.832-2.496-.832zm19.304 19.304c-1.663 1.663-8.319 6.655-14.142 4.159-5.824-2.496-2.241-5.909 2.495-7.486 2.497-.832 5.823-.833 10.814.832 2.496.831.833 2.495.833 2.495z"/><path fill="#F4900C" d="M32.314 6.317s-.145-1.727-.781-2.253c-.435-.546-2.018-.546-2.018-.546-1.664 0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f33f.svg b/browser/components/torpreferences/content/bridgemoji/1f33f.svg new file mode 100644 index 000000000000..9243e96857bf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f33f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M20.917 22.502c-2.706-.331-3.895-1.852-6.273-4.889 3.039-2.376 4.559-3.565 7.266-3.235 2.71.332 5.25 2.016 6.273 4.889-1.683 2.543-4.557 3.563-7.266 3.235zm-5.959 8.814c-2.549-.187-3.733-1.553-6.098-4.288 2.735-2.364 4.102-3.547 6.652-3.364 2.551.185 5.009 1.644 6.098 4.287-1.459 2.458-4.1 3.548-6.652 3.365zm-6.22-15.707c1.338 1.631 1.191 3.117.898 6.088-2.97-.294-4.456-.44-5.795-2.071-1.339-1.634-1.861-3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f341.svg b/browser/components/torpreferences/content/bridgemoji/1f341.svg new file mode 100644 index 000000000000..7cd7ad977b13 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f341.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M36 20.917c0-.688-2.895-.5-3.125-1s3.208-4.584 2.708-5.5-5.086 1.167-5.375.708c-.288-.458.292-3.5-.208-3.875s-5.25 4.916-5.917 4.292c-.666-.625 1.542-10.5 1.086-10.698-.456-.198-3.419 1.365-3.793 1.282C21.002 6.042 18.682 0 18 0s-3.002 6.042-3.376 6.125c-.374.083-3.337-1.48-3.793-1.282-.456.198 1.752 10.073 1.085 10.698C11.25 16.166 6.5 10.875 6 11.25s.08 3.417-.208 3.875c-.289.458-4.875-1.625-5.375-.708s [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f344.svg b/browser/components/torpreferences/content/bridgemoji/1f344.svg new file mode 100644 index 000000000000..0cf7a7914240 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f344.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M27 33c0 2.209-1.791 3-4 3H13c-2.209 0-4-.791-4-3s3-7 3-13 12-6 12 0 3 10.791 3 13z"/><path fill="#DD2E44" d="M34.666 11.189l-.001-.002c-.96-2.357-2.404-4.453-4.208-6.182h-.003C27.222 1.904 22.839 0 18 0 13.638 0 9.639 1.541 6.524 4.115c-2.19 1.809-3.941 4.13-5.076 6.785C.518 13.075 0 15.473 0 18c0 2.209 1.791 4 4 4h28c2.209 0 4-1.791 4-4 0-2.417-.48-4.713-1.334-6.811z"/><g fill="#F4ABBA"><path d="M7.708 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f345.svg b/browser/components/torpreferences/content/bridgemoji/1f345.svg new file mode 100644 index 000000000000..411c2a50e38b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f345.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M22.494 5.344c-.687 0-1.352.066-1.991.177-.819-.104-2.74-.231-3.591-.231-8.473 0-15.886 3.177-15.886 14.298 0 9.036 7.049 16.361 16.976 16.361s17.974-7.325 17.974-16.361C35.975 8.339 26.59 5.344 22.494 5.344z"/><path fill="#77B255" d="M8.439.091c1.637 1.636 2.77 2.266 3.274 4.91.298 1.564 2.266 1.51 2.266 1.51s-3.903 1.763-5.54 3.4c0 0 4.91-1.637 6.547-1.637 0 0 3.273 1.637 3.273 3.273 0 0 0-3.273-1.636-3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f346.svg b/browser/components/torpreferences/content/bridgemoji/1f346.svg new file mode 100644 index 000000000000..14688a6dd5fa --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f346.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#744EAA" d="M6 4c3 0 5 2 8 6s7.957 7.191 12 8c5 1 9 5 9 11 0 4.897-3.846 7-9 7-5 0-9-3-14-8S2 14 2 10s1-6 4-6z"/><path fill="#77B255" d="M3.515 0c1.248 0 1.248 1.248 1.248 2.495 0 1.764 1.248 1.129 2.496 1.129C8.505 3.624 11 6 11 6H7.258c-1.248 0 0 2.614-1.248 2.614S4.762 7.426 3.515 7.426 2 11 2 11s-1.604-4.153.267-6.024C3.515 3.728 1.02 0 3.515 0z"/></svg> \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f347.svg b/browser/components/torpreferences/content/bridgemoji/1f347.svg new file mode 100644 index 000000000000..e52e2f851970 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f347.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M9.999 12c-.15 0-.303-.034-.446-.106-4.38-2.19-7.484-7.526-8.501-10.578C.876.792 1.16.226 1.684.051c.525-.176 1.091.109 1.265.632.877 2.632 3.688 7.517 7.499 9.422.494.247.694.848.447 1.342-.176.351-.529.553-.896.553z"/><circle fill="#553788" cx="19" cy="29" r="7"/><circle fill="#9266CC" cx="10" cy="15" r="7"/><circle fill="#AA8DD8" cx="19" cy="12" r="7"/><circle fill="#744EAA" cx="27" cy="18" r="7"/><cir [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f348.svg b/browser/components/torpreferences/content/bridgemoji/1f348.svg new file mode 100644 index 000000000000..f3482735105c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f348.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A6D388" d="M31.924 10.982c4.418 7.652 1.99 17.326-5.424 21.607-7.414 4.28-16.505 2.413-20.924-5.241C.577 18.688 3.086 9.156 10.5 4.876c7.414-4.28 17.924.044 21.424 6.106z"/><path fill="#77B255" d="M24 6c3 1 6 4 8 7 .836 1.254.729-.078-.294-2.348C28.02 4.768 17.777.675 10.5 4.876c-3.01 1.738-5.194 4.349-6.413 7.408C4.808 11.471 5.52 11 6 11c1 0-1.896 3.279 1 11 3 8 3 4 3 4s-2-9-1-12 2-5 3-4 5 9 7 14c2.259 5.647 2-1-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f349.svg b/browser/components/torpreferences/content/bridgemoji/1f349.svg new file mode 100644 index 000000000000..0f5ec06a4d40 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f349.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M2.472 6.572C1.528 8.698 1 11.038 1 13.5 1 23.165 9.059 31 19 31c7.746 0 14.33-4.767 16.868-11.44L2.472 6.572z"/><path fill="#FFE8B6" d="M4.332 7.295C3.479 9.197 3 11.293 3 13.5c0 8.591 7.164 15.556 16 15.556 6.904 0 12.77-4.26 15.013-10.218L4.332 7.295z"/><path fill="#DD2E44" d="M6.191 8.019C5.43 9.697 5 11.548 5 13.5c0 7.518 6.268 13.611 14 13.611 6.062 0 11.21-3.753 13.156-8.995L6.191 8.019z"/><path d= [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f34a.svg b/browser/components/torpreferences/content/bridgemoji/1f34a.svg new file mode 100644 index 000000000000..82c0c52b1d0d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f34a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M3 19.5C3 10.388 10.387 3 19.499 3c9.113 0 16.5 7.387 16.5 16.5S28.612 36 19.499 36C10.387 36 3 28.613 3 19.5z"/><path fill="#662113" d="M11.414 7.585c-.267-.267-.797-.197-1.355.12-3.3-2.732-8.653-3.652-8.895-3.692-.546-.089-1.059.277-1.15.821-.091.544.276 1.06.821 1.151.053.009 4.934.854 7.821 3.16-.275.525-.324 1.015-.07 1.268.39.391 1.34.074 2.121-.707.781-.78 1.097-1.73.707-2.121z"/><path fill="#5C913 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f34b.svg b/browser/components/torpreferences/content/bridgemoji/1f34b.svg new file mode 100644 index 000000000000..ffbdc0886c9e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f34b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M11.405 3.339c6.48-1.275 8.453 1.265 11.655.084 3.202-1.181.093 2.82-.745 3.508-.84.688-8.141 4.809-11.307 3.298-3.166-1.511-3.182-6.186.397-6.89z"/><path fill="#77B255" d="M15.001 16c-.304 0-.605-.138-.801-.4-.687-.916-1.308-1.955-1.965-3.056C9.967 8.749 7.396 4.446.783 2.976c-.539-.12-.879-.654-.759-1.193.12-.54.654-.878 1.193-.759C8.671 2.68 11.599 7.581 13.952 11.519c.63 1.054 1.224 2.049 1.848 2.881. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f34c.svg b/browser/components/torpreferences/content/bridgemoji/1f34c.svg new file mode 100644 index 000000000000..b4120ba34cbf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f34c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFE8B6" d="M28 2c2.684-1.342 5 4 3 13-1.106 4.977-5 9-9 12s-11-1-7-5 8-7 10-13c1.304-3.912 1-6 3-7z"/><path fill="#FFD983" d="M31 8c0 3-1 9-4 13s-7 5-4 1 5-7 6-11 2-7 2-3z"/><path fill="#FFCC4D" d="M22 20c-.296.592 1.167-3.833-3-6-1.984-1.032-10 1-4 1 3 0 4 2 2 4-.291.292-.489.603-.622.912-.417.346-.873.709-1.378 1.088-2.263 1.697-5.84 4.227-10 7-3 2-4 3-4 4 0 3 9 3 14 1s10-7 10-7l4-4c-3-4-7-2-7-2z"/><path fill="#FF [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f34d.svg b/browser/components/torpreferences/content/bridgemoji/1f34d.svg new file mode 100644 index 000000000000..e96999db7a0e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f34d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M18.241 9.633c-.277-3.307 2.17-4.72 2.17-4.72-3.199.113-4.894 2.826-4.894 2.826-.752-1.3.946-4.012 2.169-4.719-3.198.113-3.67 2.12-3.67 2.12-1.503-2.601-1.03-4.607-1.03-4.607-1.121.647-1.767 2.113-2.141 3.512l-2.318-2.67c-.23 1.044.157 3.174.573 4.959-3.055-1.79-5.903-.15-5.903-.15 3.95 1.188 5.45 3.788 5.45 3.788s-3.948-1.187-5.646 1.526c2.597-.092 4.5.499 5.856 1.23-1.163.289-3.145-.236-4.355 1.371 0 0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f34f.svg b/browser/components/torpreferences/content/bridgemoji/1f34f.svg new file mode 100644 index 000000000000..1423d8aa8e2c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f34f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M24 7c-3 0-3 1-6 1s-3-1-6-1c-4 0-9 2-9 9 0 11 6 20 10 20 3 0 3-1 5-1s2 1 5 1c4 0 10-9 10-20 0-7.001-5-9-9-9z"/><path fill="#3E721D" d="M17.311 7.88s-1.775-4.674-6.58-6.06c-3.843-1.108-6.318.26-6.318.26s3.012 3.991 5.895 4.822c2.882.83 7.003.978 7.003.978z"/><path fill="#662113" d="M18 10c-.552 0-1-.448-1-1 0-3.441 1.2-6.615 3.293-8.707.391-.391 1.023-.391 1.414 0s.391 1.024 0 1.414C19.986 3.427 19 6.085 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f350.svg b/browser/components/torpreferences/content/bridgemoji/1f350.svg new file mode 100644 index 000000000000..2888963f457a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f350.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A6D388" d="M7.681 9.097c1.587-3.151 7.698-1.916 11.958 2.171 2.697 2.586 8.056 1.498 11.498 4.804 3.493 3.354 3.259 9.361-3.053 15.767C23 37 16 37 11.835 33.384c-4.388-3.811-2.476-8.61-4.412-13.585C5.487 14.823 3.1 9.375 7.681 9.097z"/><path fill="#662113" d="M8.178 9.534c-.43.448-1.114.489-1.527.093-3.208-3.079-3.918-7.544-3.946-7.776-.074-.586.348-1.157.939-1.278.592-.121 1.131.257 1.205.842.006.05.657 3.997 3.359 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f351.svg b/browser/components/torpreferences/content/bridgemoji/1f351.svg new file mode 100644 index 000000000000..84e81f5e6ee2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f351.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M1.062 5.125s4.875-5 10-5C17.188.125 19 5.062 19 5.062s.625-4 5-4 6.938 3.125 6.938 3.125-3.562 2.125-4.625 2.562c-2.801 1.153-11.375 3.562-15.375 2.562S1.062 5.125 1.062 5.125z"/><path fill="#FF886C" d="M18 6s1.042-.896 6-.896c6.542 0 12 4.812 12 12.927 0 11.531-14.958 17.881-14.958 17.881S1 34.833 1 17.977C1 8.018 7.75 5 12 5c4.958 0 6 1 6 1z"/><path fill="#77B255" d="M1.062 5.125s4.875-5 10-5C17.188.12 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f352.svg b/browser/components/torpreferences/content/bridgemoji/1f352.svg new file mode 100644 index 000000000000..bdba6bd679c5 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f352.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M25.999 24c-.198 0-.397-.059-.572-.181-6.543-4.58-12.166-12.366-15.397-17.439 1.699 10.528.997 16.458.962 16.744-.068.548-.562.93-1.115.868-.548-.068-.937-.567-.869-1.115.009-.079.936-8.033-1.986-21.668-.105-.487.166-.978.634-1.148.466-.172.991.028 1.226.468.079.148 8.007 14.873 17.691 21.652.453.316.562.94.246 1.392-.194.279-.504.427-.82.427z"/><path fill="#5C913B" d="M26.547 8.818c-3.476.96-5.051 2.546- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f353.svg b/browser/components/torpreferences/content/bridgemoji/1f353.svg new file mode 100644 index 000000000000..26a41ee246bf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f353.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BE1931" d="M22.614 34.845c3.462-1.154 6.117-3.034 6.12-9.373C28.736 21.461 33 17 32.999 12.921 32.998 9 28.384 2.537 17.899 3.635 7.122 4.764 3 8 2.999 15.073c0 4.927 5.304 8.381 8.127 13.518C13 32 18.551 38.187 22.614 34.845z"/><path fill="#77B255" d="M26.252 3.572c-1.278-1.044-3.28-1.55-5.35-1.677.273-.037.542-.076.82-.094.973-.063 3.614-1.232 1.4-1.087-.969.063-1.901.259-2.837.423.237-.154.479-.306.74-.442C21 0 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f354.svg b/browser/components/torpreferences/content/bridgemoji/1f354.svg new file mode 100644 index 000000000000..a129dccb3ba4 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f354.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M18 20.411c-9.371 0-16.967-.225-16.967 6.427C1.033 33.487 8.629 35 18 35c9.371 0 16.967-1.513 16.967-8.162 0-6.651-7.596-6.427-16.967-6.427z"/><path fill="#662113" d="M34.47 20.916S26.251 19.932 18 19.89c-8.251.042-16.47 1.026-16.47 1.026C.717 27.39 7.467 30.057 18 30.057s17.283-2.667 16.47-9.141z"/><path fill="#FFCC4D" d="M33.886 18.328l-31.855.646c-1.1 0-2.021 2.229-.854 2.812 8.708 2.708 15.708 5.448 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f355.svg b/browser/components/torpreferences/content/bridgemoji/1f355.svg new file mode 100644 index 000000000000..3a44bba9a045 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f355.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M18 4c7.257 0 13 4 14.699 2 .197-.323.301-.657.301-1 0-2-6.716-5-15-5C9.716 0 3 3 3 5c0 .343.104.677.301 1C5 8 10.743 4 18 4z"/><path fill="#FFCC4D" d="M18 3C11.787 3 7.384 4.81 5.727 5.618c-.477.233-.539.84-.415 1.278S16 34 16 34s.896 2 2 2 2-2 2-2L30.704 6.779s.213-.842-.569-1.229C28.392 4.689 24.047 3 18 3z"/><g fill="#BE1931"><path d="M18 31c0-2.208-1.791-4-4-4-.254 0-.5.029-.741.075L16 34s.071.14.19. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f368.svg b/browser/components/torpreferences/content/bridgemoji/1f368.svg new file mode 100644 index 000000000000..187b2f4c9a41 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f368.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M20 28.625S30.661 25.9 33.356 15.72c.396-1.495-1.518-2.72-3.868-2.72H5.999c-1.175 0-3.74.493-3.072 2.894C5.711 25.9 16 28.625 16 28.625v1.173s-4.634 2.443-5.588 3.01c-1.027.588-.268 1.526.144 1.689.684.269 2.39 1.15 7.116 1.15 4.847 0 7.497-.954 8.083-1.15.226-.075 1.197-.973-.198-1.799-2.484-1.47-5.557-2.9-5.557-2.9v-1.173z"/><path fill="#3B88C3" d="M33.291 15.248c0 1.692-6.835 3.064-15.269 3.064-8.432 0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f369.svg b/browser/components/torpreferences/content/bridgemoji/1f369.svg new file mode 100644 index 000000000000..3c2aa5826ec6 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f369.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M35.337 23.549C34.291 29.819 27.615 34 18.092 34S1.894 29.819.849 23.549c-.247-1.479.156-8.12 1.054-9.406 2.559-3.664 3.474 10.365 16.189 10.365 13.847 0 13.64-14.028 16.199-10.365.898 1.286 1.292 7.927 1.046 9.406z"/><path fill="#8A4B38" d="M18.092 5.995c-9.331 0-16.895 4.584-16.895 10.239 0 5.655 7.564 10.24 16.895 10.24 9.33 0 16.895-4.585 16.895-10.24S27.422 5.995 18.092 5.995zm0 13.374c-3.174 0-5.748 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f36a.svg b/browser/components/torpreferences/content/bridgemoji/1f36a.svg new file mode 100644 index 000000000000..4f5368a41dd2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f36a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DA9F83" d="M34.966 17.87c.414 4.649-2.082 8.731-4.831 12.206-2.304 2.554-5.602 3.631-8.901 4.285-3.198 1.089-6.766.739-9.716-.895-1.034-.43-2.248-.559-3.167-1.176-2.879-1.846-4.524-4.927-5.779-8.029-1.627-2.916-1.74-6.483-1.414-9.742.219-1.107.967-2.032 1.388-3.051.729-2.127 1.916-3.963 3.569-5.475.786-.787 1.377-1.823 2.303-2.444.919-.617 2.103-.758 3.137-1.188 1.016-.422 1.968-1.08 3.072-1.299 1.072-.213 2.201.106 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f36b.svg b/browser/components/torpreferences/content/bridgemoji/1f36b.svg new file mode 100644 index 000000000000..a993c9b4b7f8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f36b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M26.339 33.496c-1.562 1.561-4.095 1.561-5.657 0L1.59 14.404c-1.562-1.562-1.562-2.681 0-4.243l8.485-8.485c1.562-1.562 2.681-1.562 4.243 0L33.41 20.768c1.562 1.562 1.562 4.095 0 5.657l-7.071 7.071z"/><path fill="#8A4B38" d="M16.582 15.253l-4.885-4.885 5.657-5.657-1.415-1.414-5.656 5.657L6.54 5.211 5.126 6.626l3.743 3.742-5.657 5.657 1.414 1.414 5.657-5.657 4.885 4.885z"/><path fill="#DD2E44" d="M26.339 34.9 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f36c.svg b/browser/components/torpreferences/content/bridgemoji/1f36c.svg new file mode 100644 index 000000000000..f6fbf3b70bba --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f36c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M13.298 3.538c-.531-4.075-5.627-4.3-6.248-2.125l-.933 4.041-4.041.933c-2.175.621-1.949 5.717 2.125 6.248l3.054.399c4.074.531 6.973-2.367 6.441-6.441l-.398-3.055zm18.993 18.991c4.074.531 4.299 5.629 2.125 6.249l-4.041.933-.934 4.042c-.62 2.174-5.717 1.948-6.248-2.126l-.398-3.055c-.531-4.074 2.367-6.973 6.441-6.441l3.055.398z"/><path fill="#BE1931" d="M30.49 22.294l-1.254-.164c-4.074-.531-6.973 2.367-6.441 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f36d.svg b/browser/components/torpreferences/content/bridgemoji/1f36d.svg new file mode 100644 index 000000000000..e13447edec3c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f36d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M35.066 35.066c-.766.766-2.006.766-2.771 0L12.198 14.97c-.766-.766-.766-2.007 0-2.773.766-.765 2.007-.765 2.772 0l20.096 20.098c.766.764.766 2.006 0 2.771z"/><path fill="#DD2E44" d="M27.8 14.08c0 7.576-6.142 13.72-13.72 13.72C6.503 27.8.36 21.656.36 14.08.36 6.502 6.503.36 14.08.36c7.578 0 13.72 6.142 13.72 13.72z"/><path fill="#F4900C" d="M17.411 27.376c-1.459.092-2.938-.066-4.379-.503-3.156-.961-5.748-3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f37f.svg b/browser/components/torpreferences/content/bridgemoji/1f37f.svg new file mode 100644 index 000000000000..ddbff6d95f16 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f37f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M31.301 11H4.668c-1.657 0-3 1.343-3 3s1.343 3 3 3c.182 0 .357-.023.53-.053L5.182 17l.637 14.004C5.918 33.202 7.8 35 10 35h16c2.2 0 4.082-1.798 4.182-3.996L30.818 17l-.015-.05c.163.027.327.05.498.05 1.657 0 3-1.343 3-3s-1.344-3-3-3z"/><path d="M28.668 17v16.989c.863-.734 1.444-1.796 1.492-2.986L30.84 17h-2.172zm-6 0h3v18h-3zm-6 18V17h3v18zm-6-18h3v18h-3zM5.16 17l.68 14.003c.054 1.342.776 2.528 1.828 3.254V [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f380.svg b/browser/components/torpreferences/content/bridgemoji/1f380.svg new file mode 100644 index 000000000000..03d4a7510652 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f380.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M19.281 6.392c0 .405-2.37.405-2.37 0 0-.9-14.911-9.899-14.911-2.7v13.499c0 2.487 3.476 1.947 7.193.361-1.429 3.525-4.498 9.68-7.05 13.934-.229.382-.178.868.124 1.194.303.325.783.415 1.181.215l5.258-2.629c.441.726.931 1.868 1.376 2.906.333.778.678 1.582 1.024 2.275.144.287.417.488.734.54.053.009.107.013.16.013.263 0 .518-.104.707-.293 1.42-1.419 3.429-8.395 4.793-14.093 1.364 5.698 3.373 12.674 4.793 14.09 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f381.svg b/browser/components/torpreferences/content/bridgemoji/1f381.svg new file mode 100644 index 000000000000..1ab82981c057 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f381.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FDD888" d="M33 31c0 2.2-1.8 4-4 4H7c-2.2 0-4-1.8-4-4V14c0-2.2 1.8-4 4-4h22c2.2 0 4 1.8 4 4v17z"/><path fill="#FDD888" d="M36 11c0 2.2-1.8 4-4 4H4c-2.2 0-4-1.8-4-4s1.8-4 4-4h28c2.2 0 4 1.8 4 4z"/><path fill="#FCAB40" d="M3 15h30v2H3z"/><path fill="#DA2F47" d="M19 3h-2c-1.657 0-3 1.343-3 3v29h8V6c0-1.656-1.343-3-3-3z"/><path fill="#DA2F47" d="M16 7c1.1 0 1.263-.516.361-1.147L9.639 1.147c-.902-.631-2.085-.366-2.631.589 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f382.svg b/browser/components/torpreferences/content/bridgemoji/1f382.svg new file mode 100644 index 000000000000..35f9a002a9a7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f382.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#8899A6" cx="18" cy="26" rx="18" ry="10"/><ellipse fill="#CCD6DD" cx="18" cy="24.25" rx="18" ry="10"/><path fill="#DD2E44" d="M32.675 23.685c0 4.26-6.57 7.712-14.675 7.712S3.325 27.945 3.325 23.685c0-4.258 6.57-7.711 14.675-7.711 8.104 0 14.675 3.453 14.675 7.711z"/><path fill="#F4ABBA" d="M32.233 22.543c0 9.854-28.466 9.854-28.466 0v-8.759h28.466v8.759z"/><path fill="#DD2E44" d="M17.984 18.166c-8.984 0-14.218-4.1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f383.svg b/browser/components/torpreferences/content/bridgemoji/1f383.svg new file mode 100644 index 000000000000..591fc66a439d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f383.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M32.664 8.519C29.364 5.134 23.42 4.75 18 4.75S6.636 5.134 3.336 8.519C.582 11.344 0 15.751 0 19.791c0 5.263 1.982 11.311 6.357 14.244C9.364 36.051 13.95 35.871 18 35.871s8.636.18 11.643-1.836C34.018 31.101 36 25.054 36 19.791c0-4.04-.582-8.447-3.336-11.272z"/><path fill="#3F7123" d="M20.783 5.444c.069.42-.222.764-.647.764h-4.451c-.426 0-.717-.344-.647-.764l.745-4.472c.07-.421.476-.764.902-.764h2.451c.426 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f388.svg b/browser/components/torpreferences/content/bridgemoji/1f388.svg new file mode 100644 index 000000000000..6d431bc8568d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f388.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FCAB40" d="M27.999 36c-.448 0-.855-.303-.969-.758-.036-.136-.926-3.436-4.273-4.272C18.054 29.794 18 23.277 18 23c0-.552.447-.998.998-.999.554.034 1 .445 1.002.997 0 .053.054 5.234 3.242 6.032 4.53 1.132 5.682 5.54 5.727 5.728.135.535-.191 1.078-.727 1.212-.081.02-.163.03-.243.03z"/><path fill="#BB1A34" d="M18 25c-1.1 0-1.598-.805-1.105-1.789l1.211-2.422c.492-.984 1.297-.984 1.789 0l1.211 2.422C21.598 24.195 21.1 25 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f389.svg b/browser/components/torpreferences/content/bridgemoji/1f389.svg new file mode 100644 index 000000000000..a4b8305af6be --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f389.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"/><path fill="#EA596E" d="M13 12L.416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"/><path fill="# [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f38f.svg b/browser/components/torpreferences/content/bridgemoji/1f38f.svg new file mode 100644 index 000000000000..5457d1e8d5f1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f38f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M3 3h2v33H3z"/><circle fill="#F4900C" cx="4" cy="3" r="3"/><path fill="#66757F" d="M9 28H4c-.552 0-1-.447-1-1s.448-1 1-1h5c.552 0 1 .447 1 1s-.448 1-1 1z"/><path fill="#DD2E44" d="M31 27c0-2.209 6.209-6 4-6h-8.447c-1.203-1.464-4.595-3-7.053-3-2.459 0-3.23 1.536-3.435 3H10c-2.209 0-3 1.791-3 4v4c0 2.209.791 4 3 4h25c2.209 0-4-3.791-4-6z"/><circle fill="#FFF" cx="12" cy="25" r="3"/><circle cx="11.5" cy="24. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f392.svg b/browser/components/torpreferences/content/bridgemoji/1f392.svg new file mode 100644 index 000000000000..f44d568064f5 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f392.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M11.946 27C-1.036 7.876 7.524 2.752 9.114 2c.149-.07.242-.105.245-.107.418-.209.638-.675.529-1.125C9.779.318 9.372 0 8.903 0H4.847C1.054 0-4.282 11 5.859 28c.151.253 5.073 0 5.073 0 .559 0 1.324-.541 1.014-1zM31.229 0h-4c-.462 0-.863.318-.971.768-.107.45.109.916.521 1.125.004.002.095.037.242.107 1.568.752 10.01 5.876-2.792 25-.307.459.448 1 1 1 0 0 4.854.253 5.002 0 10-17 4.739-28 .998-28z" fill="#C1694F"/><path d="M6.8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f399.svg b/browser/components/torpreferences/content/bridgemoji/1f399.svg new file mode 100644 index 000000000000..07881e4aabe6 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f399.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M6 15h24v2H6zm15 15c0 2.208-.792 4-3 4-2.209 0-3-1.792-3-4s.791-2 3-2c2.208 0 3-.208 3 2z"/><path fill="#66757F" d="M18 31c-6.627 0-10 1.343-10 3v2h20v-2c0-1.657-3.373-3-10-3z"/><path fill="#99AAB5" d="M18 0c-4.971 0-9 4.029-9 9v7h18V9c0-4.971-4.029-9-9-9z"/><g fill="#292F33"><circle cx="15.5" cy="2.5" r="1.5"/><circle cx="20.5" cy="2.5" r="1.5"/><circle cx="17.5" cy="6.5" r="1.5"/><circle cx="22.5" cy="6 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f39f.svg b/browser/components/torpreferences/content/bridgemoji/1f39f.svg new file mode 100644 index 000000000000..984f27064942 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f39f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EA596E" d="M26.751 8.007c1.364 1.365 3.364 1.708 5.046 1.032l3.613 3.611c.737.74.738 1.938-.004 2.68L15.319 35.405c-.743.739-1.94.737-2.681 0l-3.606-3.612c.672-1.684.33-3.682-1.032-5.047-1.367-1.365-3.364-1.707-5.047-1.032l-2.359-2.36c-.74-.737-.742-1.938 0-2.68L20.678.596c.739-.74 1.936-.741 2.679.002l2.361 2.361c-.677 1.683-.331 3.681 1.033 5.048z"/><path fill="#BE1931" d="M5.42 18.527l-2.149 2.148c-.739.739-.741 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3a0.svg b/browser/components/torpreferences/content/bridgemoji/1f3a0.svg new file mode 100644 index 000000000000..35c75b697034 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3a0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M24.357 16.337s2.248-3.959 4.805-4.169c2.557-.21 3.929.934 4.144 3.432.287 3.334-.878 3.802-.033 7.53 0 0-3.546-2.414-2.421-6.07 1.172-3.806-4.81-3.095-3.835.316l-2.66-1.039z"/><path fill="#FFCC4D" d="M14.31 20.375c-.553 0-1 .448-1 1v13.563c0 .552.447 1 1 1s1-.448 1-1V21.375c0-.552-.448-1-1-1z"/><path fill="#F4900C" d="M15.31 25.77l-2 1.047v2.257l2-1.047zm-2 8.054l2-1.047V30.52l-2 1.047z"/><path fill="#88 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3a1.svg b/browser/components/torpreferences/content/bridgemoji/1f3a1.svg new file mode 100644 index 000000000000..c35744ab8c82 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3a1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M30.806 15.347H19.201l8.205-8.205-.707-.707-8.205 8.205V3.036h-1V14.64L9.288 6.435l-.707.707 8.206 8.205H5.182v1h11.605l-8.206 8.206.707.707 8.206-8.206v11.605h1V17.055l8.205 8.205.707-.707-8.205-8.206h11.605z"/><path fill="#AAB8C2" d="M17.994 1.394c-7.982 0-14.453 6.471-14.453 14.453 0 7.982 6.471 14.453 14.453 14.453 7.983 0 14.454-6.471 14.454-14.453-.001-7.982-6.472-14.453-14.454-14.453zm0 26.683c-6.7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3a2.svg b/browser/components/torpreferences/content/bridgemoji/1f3a2.svg new file mode 100644 index 000000000000..256d8afb7df4 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3a2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M32.612 16.645v-1h-4.083V8.333h-1v7.312h-3.11v1h3.11v4H18.55v-4.604h-1v4.604H12.8v1h4.75v4H8.612v-3.208h-1v3.208H2.205v1h5.407v4H2.205v1h5.407V36h1v-4.355h8.938V36h1v-4.355h8.979V36h1v-4.355h4.083v-1h-4.083v-4h4.083v-1h-4.083v-4h4.083v-1h-4.083v-4h4.083zm-24 14v-4h8.938v4H8.612zm18.917 0H18.55v-4h8.979v4zm0-5H18.55v-4h8.979v4z"/><path fill="#CCD6DD" d="M31.05 6.595h4V36h-4z"/><path fill="#8899A6" d="M34.0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3a8.svg b/browser/components/torpreferences/content/bridgemoji/1f3a8.svg new file mode 100644 index 000000000000..3bfdea0c95bf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3a8.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M18 3.143c-9.941 0-18 6.908-18 15.428 0 1.066.126 2.107.367 3.112C2.146 24.744 3.377 22.812 9 20c5.727-2.864 0 4-2 8-.615 1.23-.282 2.271.56 3.124C10.506 32.928 14.104 34 18 34c9.941 0 18-6.907 18-15.429 0-8.52-8.059-15.428-18-15.428zm2.849 24.447c-.395 1.346-2.46 1.924-4.613 1.291-2.153-.632-3.578-2.234-3.183-3.581.395-1.346 2.46-1.924 4.613-1.29 2.153.631 3.578 2.233 3.183 3.58z"/><circle fill="#5C913B" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3ac.svg b/browser/components/torpreferences/content/bridgemoji/1f3ac.svg new file mode 100644 index 000000000000..3a326766110e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3ac.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3F7123" d="M35.845 32c0 2.2-1.8 4-4 4h-26c-2.2 0-4-1.8-4-4V19c0-2.2 1.8-4 4-4h26c2.2 0 4 1.8 4 4v13z"/><path fill="#3F7123" d="M1.845 15h34v6h-34z"/><path fill="#CCD6DD" d="M1.845 15h34v7h-34z"/><path fill="#292F33" d="M1.845 15h4l-4 7v-7zm11 0l-4 7h7l4-7h-7zm14 0l-4 7h7l4-7h-7z"/><path fill="#CCD6DD" d="M.155 8.207L33.148 0l1.69 6.792L1.845 15z"/><path fill="#292F33" d="M.155 8.207l5.572 5.827L1.845 15 .155 8.207zm [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3af.svg b/browser/components/torpreferences/content/bridgemoji/1f3af.svg new file mode 100644 index 000000000000..073817f2f316 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3af.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#DD2E44" cx="18" cy="18" r="18"/><circle fill="#FFF" cx="18" cy="18" r="13.5"/><circle fill="#DD2E44" cx="18" cy="18" r="10"/><circle fill="#FFF" cx="18" cy="18" r="6"/><circle fill="#DD2E44" cx="18" cy="18" r="3"/><path opacity=".2" d="M18.24 18.282l13.144 11.754s-2.647 3.376-7.89 5.109L17.579 18.42l.661-.138z"/><path fill="#FFAC33" d="M18.294 19c-.255 0-.509-.097-.704-.292-.389-.389-.389-1.018 0-1.407l.563-.563c. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3b2.svg b/browser/components/torpreferences/content/bridgemoji/1f3b2.svg new file mode 100644 index 000000000000..408f2f9206f7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3b2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M4 11v.137c0-.042.011-.084.015-.126L4 11zm13 11.137c-.146-.067-.287-.145-.412-.246L4.565 12.245C4.188 11.94 4 11.539 4 11.137v12.238c0 2.042 1.093 2.484 1.093 2.484l11.574 9.099c.205.161.377.259.528.318-.114-.174-.195-.375-.195-.604V22.137zm-8.773.363c-.994 0-2.033-1.007-2.319-2.25-.287-1.242.287-2.249 1.28-2.249.994 0 2.033 1.007 2.319 2.249.287 1.243-.286 2.25-1.28 2.25zM13.81 30c-.994 0-2.033-1.008-2.3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3b6.svg b/browser/components/torpreferences/content/bridgemoji/1f3b6.svg new file mode 100644 index 000000000000..f13b3b8bfb9a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3b6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5DADEC" d="M14.182.168L7.818 1.469C7.368 1.561 7 2.012 7 2.471v15.857C6.387 18.12 5.712 18 5 18c-2.762 0-5 1.741-5 3.889 0 2.147 2.238 3.889 5 3.889 2.713 0 4.908-1.683 4.985-3.777H10V6.477l4.182-.855c.45-.092.818-.543.818-1.002V.835c0-.459-.368-.76-.818-.667zm21 4l-6.363 1.301c-.451.092-.819.543-.819 1.002v15.857c-.613-.209-1.288-.328-2-.328-2.762 0-5 1.741-5 3.889 0 2.147 2.238 3.889 5 3.889 2.713 0 4.908-1.683 4. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3b7.svg b/browser/components/torpreferences/content/bridgemoji/1f3b7.svg new file mode 100644 index 000000000000..ed0f849e0d99 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3b7.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill-rule="evenodd" clip-rule="evenodd" fill="#FCAB40" d="M14 16L9 26s-1 2 1 2c1 0 2-2 2-2L26 7s2-4 8-1v2c-3-1-4 1-4 1L15 33s-2 3-7 3c-6 0-7-5-7-8 0-2 1-4 2-6s-2-6-2-6h13z"/><path fill="#FDCB58" d="M7.5 20C4.04 20 0 18.952 0 16c0-2.953 4.04-4 7.5-4s7.5 1.047 7.5 4c0 2.952-4.04 4-7.5 4z"/><circle fill="#CCD6DD" cx="19" cy="17" r="2"/><circle fill="#CCD6DD" cx="22" cy="13" r="2"/><circle fill="#CCD6DD" cx="25" cy="9" r="2"/> [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3b8.svg b/browser/components/torpreferences/content/bridgemoji/1f3b8.svg new file mode 100644 index 000000000000..22074a11f3f0 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3b8.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BB1A34" d="M21.828 20.559C19.707 21.266 19 17.731 19 17.731s.965-.968.235-1.829c1.138-1.137.473-1.707.473-1.707-1.954-1.953-5.119-1.953-7.071 0-.246.246-.414.467-.553.678-.061.086-.115.174-.17.262l-.014.027c-.285.475-.491.982-.605 1.509-.156.319-.379.659-.779 1.06-1.414 1.414-4.949-.707-7.778 2.121-.029.029-.045.069-.069.104-.094.084-.193.158-.284.25-3.319 3.319-3.003 9.018.708 12.728 3.524 3.525 8.84 3.979 12.209 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3ba.svg b/browser/components/torpreferences/content/bridgemoji/1f3ba.svg new file mode 100644 index 000000000000..454ab7818ac2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3ba.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FCAB40" d="M5.622 33.051l-2.674-2.673L23.337 9.987c3.344-3.343 1.337-8.021 2.007-8.689.666-.67 1.335-.002 1.335-.002l8.023 8.023c.668.668 0 1.336 0 1.336-.669.67-5.778-.908-8.692 2.006L5.622 33.051z"/><path fill="#CCD6DD" d="M5.457 33.891c.925-.925.925-2.424 0-3.35-.924-.924-2.424-.924-3.349 0l.087.087c-.371-.334-.938-.331-1.296.027-.369.368-.369.968 0 1.336L4.008 35.1c.37.369.968.369 1.337 0 .355-.356.36-.919.032-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3bb.svg b/browser/components/torpreferences/content/bridgemoji/1f3bb.svg new file mode 100644 index 000000000000..efb7d5da7eeb --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3bb.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F18F26" d="M23.912 12.087C22.219 10.394 20.318 9.5 18.415 9.5c-1.626 0-3.189.667-4.402 1.88-1.519 1.519-1.727 2.39-1.865 2.966-.071.295-.106.421-.255.57-.106.106-.155.256-.14.406.015.149.1.286.225.369.013.009.324.22.368.651.039.394-.13 1.08-1.16 2.11-.629.629-1.252.948-1.85.948-.981 0-1.649-.87-1.654-.877-.11-.15-.295-.226-.48-.197-.185.029-.337.159-.396.335-.221.663-.251.668-.535.709-.59.086-1.578.229-3.624 2.275C. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3be.svg b/browser/components/torpreferences/content/bridgemoji/1f3be.svg new file mode 100644 index 000000000000..323e5c462ecf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3be.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#77B255" cx="18" cy="18" r="18"/><path fill="#A6D388" d="M26 18c0 6.048 2.792 10.221 5.802 11.546C34.42 26.42 36 22.396 36 18c0-4.396-1.58-8.42-4.198-11.546C28.792 7.779 26 11.952 26 18z"/><path fill="#FFF" d="M27 18c0-6.048 1.792-10.221 4.802-11.546-.445-.531-.926-1.028-1.428-1.504C27.406 6.605 25 10.578 25 18c0 7.421 2.406 11.395 5.374 13.05.502-.476.984-.973 1.428-1.504C28.792 28.221 27 24.048 27 18z"/><path fil [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3c0.svg b/browser/components/torpreferences/content/bridgemoji/1f3c0.svg new file mode 100644 index 000000000000..24693956f6f9 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3c0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#F4900C" cx="18" cy="18" r="18"/><path fill="#231F20" d="M36 17h-8.981c.188-5.506 1.943-9.295 4.784-10.546-.445-.531-.926-1.027-1.428-1.504-2.83 1.578-5.145 5.273-5.354 12.049H19V0h-2v17h-6.021c-.208-6.776-2.523-10.471-5.353-12.049-.502.476-.984.972-1.428 1.503C7.039 7.705 8.793 11.494 8.981 17H0v2h8.981c-.188 5.506-1.942 9.295-4.783 10.546.445.531.926 1.027 1.428 1.504 2.831-1.578 5.145-5.273 5.353-12.05H17v17h2V1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3c6.svg b/browser/components/torpreferences/content/bridgemoji/1f3c6.svg new file mode 100644 index 000000000000..00457c31ea30 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3c6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M5.123 5h6C12.227 5 13 4.896 13 6V4c0-1.104-.773-2-1.877-2h-8c-2 0-3.583 2.125-3 5 0 0 1.791 9.375 1.917 9.958C2.373 18.5 4.164 20 6.081 20h6.958c1.105 0-.039-1.896-.039-3v-2c0 1.104-.773 2-1.877 2h-4c-1.104 0-1.833-1.042-2-2S3.539 7.667 3.539 7.667C3.206 5.75 4.018 5 5.123 5zm25.812 0h-6C23.831 5 22 4.896 22 6V4c0-1.104 1.831-2 2.935-2h8c2 0 3.584 2.125 3 5 0 0-1.633 9.419-1.771 10-.354 1.5-2.042 3-4 3h- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3c8.svg b/browser/components/torpreferences/content/bridgemoji/1f3c8.svg new file mode 100644 index 000000000000..4f5530d29087 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3c8.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M7.754 7.754C4.068 11.44 1.64 16.37.843 21.335l13.822 13.822c4.965-.797 9.895-3.225 13.581-6.911s6.114-8.616 6.911-13.581L21.335.843C16.37 1.64 11.44 4.068 7.754 7.754zm25.615-5.123C30.707 1.152 27.634.51 24.472.564l10.965 10.964c.053-3.162-.589-6.235-2.068-8.897zM2.631 33.369c2.662 1.479 5.736 2.121 8.898 2.067L.564 24.472c-.054 3.161.588 6.235 2.067 8.897z"/><path fill="#E1E8ED" d="M22.828 11.757l-2.414 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3d3.svg b/browser/components/torpreferences/content/bridgemoji/1f3d3.svg new file mode 100644 index 000000000000..6201ef5ae20b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3d3.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M23.106 5.971C17.615.48 8.384-.521 3.307 4.557-1.77 9.634-.77 18.865 4.721 24.356c3.554 3.554 7.785 4.323 11.707 3.058l-.015.013.13-.052c.264-.088.527-.179.788-.284.698-.238 1.734-.558 2.942-.806 1.848-.38 3.541 1.606 4.955 3.021 1.414 1.414 4.242 5.657 4.949 6.364.707.707 1.414 0 2.122-.707l.707-.707.707-.707c.707-.708 1.414-1.415.707-2.122-.707-.707-4.95-3.535-6.364-4.949-1.414-1.414-3.4-3.107-3.021-4.9 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3d4.svg b/browser/components/torpreferences/content/bridgemoji/1f3d4.svg new file mode 100644 index 000000000000..8b78f31e4c64 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3d4.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M19.083 35.5L12.25 12.292s-1.42.761-2.604 1.637c-.313.231-1.977 2.79-2.312 3.04-1.762 1.315-3.552 2.841-3.792 3.167C3.083 20.76 0 36 0 36l19.083-.5z"/><path fill="#4B545D" d="M32 35l-5.5-19.75L18 4.542l-5.373 4.193-.971 1.172C11.25 10.688 10 15.25 10 15.25L8.75 20 3.252 30.054.917 34.791 32 35z"/><path fill="#FFF" d="M3.252 30.054s7.873-10.783 7.894-11.388C11.167 18.062 10 15.25 10 15.25L8.75 20 3.252 30. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3d5.svg b/browser/components/torpreferences/content/bridgemoji/1f3d5.svg new file mode 100644 index 000000000000..7a2fb80eddb7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3d5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#78B159" d="M18 25.18C3.438 25.18 0 29.844 0 32s1.791 4 4 4h28c2.209 0 4-1.851 4-4s-3.438-6.82-18-6.82z"/><path fill="#662113" d="M28.804 28.15c0 1.531-1.242 2.08-2.773 2.08-1.531 0-2.773-.548-2.773-2.08l.693-6.239c0-1.531.548-1.387 2.08-1.387 1.531 0 2.08-.145 2.08 1.387l.693 6.239z"/><path fill="#5C913B" d="M34.868 19.717c-4.59-4.135-6.449-10.056-8.837-10.056s-4.247 5.921-8.836 10.056c-1.929 1.738-1.01 2.743 1.634 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3dd.svg b/browser/components/torpreferences/content/bridgemoji/1f3dd.svg new file mode 100644 index 000000000000..d66d8d477b43 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3dd.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#3B88C3" cx="18" cy="30.54" rx="18" ry="5.36"/><path fill="#88C9F9" d="M33.812 28.538c0 1.616-2.5 2.587-14.482 2.587-10.925 0-13.612-.971-13.612-2.587s5.683-2.926 13.612-2.926 14.482 1.31 14.482 2.926z"/><path fill="#F4900C" d="M7 28.25c0-1 1-5 11-5 12 0 15 4 15 5s0 2-14 2c-12 0-12-1-12-2z"/><circle fill="#FFCC4D" cx="7" cy="6.25" r="5"/><circle fill="#FFAC33" cx="7" cy="6.25" r="4"/><path fill="#C1694F" d="M26.94 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3e1.svg b/browser/components/torpreferences/content/bridgemoji/1f3e1.svg new file mode 100644 index 000000000000..b44b7288b1d6 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3e1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M36 33.5c0 .828-.672 1.5-1.5 1.5h-33C.672 35 0 34.328 0 33.5S.672 32 1.5 32h33c.828 0 1.5.672 1.5 1.5z"/><path fill="#A0041E" d="M12.344 14.702h-2c-.276 0-.5-.224-.5-.5v-7c0-.276.224-.5.5-.5h2c.276 0 .5.224.5.5v7c0 .276-.224.5-.5.5z"/><path fill="#FFCC4D" d="M5.942 32c-.137-4.657-.506-8-.942-8-.435 0-.804 3.343-.941 8h1.883z"/><path fill="#77B255" d="M10 18.731C10 24.306 7.762 26 5 26c-2.761 0-5-1.694-5-7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3ee.svg b/browser/components/torpreferences/content/bridgemoji/1f3ee.svg new file mode 100644 index 000000000000..a825f2b79b1c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3ee.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M29 34c0 1.104-.896 2-2 2H9c-1.104 0-2-.896-2-2V2c0-1.104.896-2 2-2h18c1.104 0 2 .896 2 2v32z"/><path fill="#BE1931" d="M6.699 32h22.602C33.383 28.7 35 23.658 35 18c0-5.658-1.616-10.7-5.698-14H6.698C2.615 7.3 1 12.342 1 18c0 5.658 1.616 10.7 5.699 14z"/><path d="M1.301 22c.108.682.245 1.35.415 2h32.568c.17-.65.307-1.318.415-2H1.301zm-.229-2h33.855c.049-.657.073-1.324.073-2H1c0 .676.024 1.343.072 2zm31.605 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3f7.svg b/browser/components/torpreferences/content/bridgemoji/1f3f7.svg new file mode 100644 index 000000000000..60462664ebd6 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3f7.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M32.017 20.181L17.345 5.746C16.687 5.087 15.823 5 14.96 5H4.883C3.029 5 2 6.029 2 7.883v10.082c0 .861.089 1.723.746 2.38L17.3 35.017c1.311 1.31 3.378 1.31 4.688 0l10.059-10.088c1.31-1.312 1.28-3.438-.03-4.748zm-23.596-8.76c-.585.585-1.533.585-2.118 0s-.586-1.533 0-2.118c.585-.586 1.533-.585 2.118 0 .585.586.586 1.533 0 2.118z"/><path fill="#D99E82" d="M9.952 7.772c-1.43-1.431-3.749-1.431-5.179 0-1.431 1.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3f8.svg b/browser/components/torpreferences/content/bridgemoji/1f3f8.svg new file mode 100644 index 000000000000..143f8eaedb2d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3f8.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M30.385 29c.382.382.382 1.002 0 1.385-.383.382-1.003.382-1.385 0L15.5 16.884c-.382-.382-.382-1.002 0-1.384.382-.382 1.001-.382 1.384 0L30.385 29z"/><path fill="#292F33" d="M35.561 33.439c.586.586.586 1.536 0 2.121-.585.586-1.535.586-2.121 0l-5.656-5.656c-.586-.586-.586-1.536 0-2.121.585-.586 1.535-.586 2.121 0l5.656 5.656z"/><g fill="#99AAB5"><path d="M2.447 5.2l.707-.707L15.178 16.51l-.707.707zm1.417-2.8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f3f9.svg b/browser/components/torpreferences/content/bridgemoji/1f3f9.svg new file mode 100644 index 000000000000..37922127ee96 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f3f9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M21.279 4.28c-9 0-10.5 1.5-13 4s-4 4-4 13c0 11.181-2 14-1 14s3-2 3-4-2-13 5-20h-.001c7-6.999 18-5 20-5s4-2 4-3-2.818 1-13.999 1z"/><path fill="#D99E82" d="M29.5 29.779c0 .276-.224.5-.5.5H3.78c-.276 0-.5-.224-.5-.5s.224-.5.5-.5H29c.276 0 .5.224.5.5zm.279-.279c-.277 0-.5-.225-.5-.5V3.779c0-.276.223-.5.5-.5.275 0 .5.224.5.5V29c0 .275-.224.5-.5.5z"/><path fill="#99AAB5" d="M0 0l2.793 8.52L4.93 4.955l3.52-2.09 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f40a.svg b/browser/components/torpreferences/content/bridgemoji/1f40a.svg new file mode 100644 index 000000000000..a9a6debcc432 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f40a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M19 32c0 1-1.723 3-3.334 3C14.056 35 14 33.657 14 32s1.306-3 2.916-3C18.527 29 19 30.343 19 32zm11 0c0 1-1.723 3-3.334 3C25.056 35 25 33.657 25 32s1.306-3 2.916-3C29.527 29 30 30.343 30 32z"/><path fill="#5C913B" d="M36 25c0-6-3.172-9.171-6-12-1-1-1.399.321-1 1 .508.862 3 8-2 8h-2c-5 0-6.172-1.172-9-4-4.5-4.5-7 0-9 0-6 0-7-1.812-7 2 0 3 3 4 6 4s3 1 5 4c1.071 1.606 2.836 3.211 5.023 4.155.232 1.119 2.774 3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f40c.svg b/browser/components/torpreferences/content/bridgemoji/1f40c.svg new file mode 100644 index 000000000000..2c63658289ae --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f40c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#9266CC" d="M9 15.561c0-4 2-8 0-10s-3 2-2 2c1.414 0 2 2 1 5s1 3.999 1 3zm-3.875 0c0-4 2-8 0-10s-3 2-2 2c1.414 0 2 2 1 5s1 3.999 1 3z"/><path fill="#AA8DD8" d="M36 34.936c0 1-1 1-2 1H11c-5 0-7.272-3.09-8-6-1-4 2-11 0-12s-3-1-3-3 4.47-5.265 7-4c4 2 2.767 6.932 2 10-.88 3.522 1.115 3.594 5 5 4.94 1.787 12.32 4.44 14 5 3 1 8 3 8 4z"/><path fill="#FFCC4D" d="M10.925 24.935c2.887 1 3.849 9 13.472 9 6.377 0 8.66-5.479 8.66- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f40d.svg b/browser/components/torpreferences/content/bridgemoji/1f40d.svg new file mode 100644 index 000000000000..17531783ba93 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f40d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M11.84 7.634c-.719 0-2.295 2.243-3.567 1.029-.44-.419 1.818-1.278 1.727-2.017-.075-.607-2.842-1.52-1.875-2.099.967-.578 2.418.841 3.513.866 2.382.055 4.212-.853 4.238-.866.541-.274 1.195-.052 1.464.496.27.547.051 1.213-.488 1.486-.131.066-2.225 1.105-5.012 1.105z"/><path fill="#77B255" d="M27.818 36c-3.967 0-8.182-2.912-8.182-8.308 0-1.374-.89-1.661-1.637-1.661-.746 0-1.636.287-1.636 1.661 0 5.396-4.216 8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f417.svg b/browser/components/torpreferences/content/bridgemoji/1f417.svg new file mode 100644 index 000000000000..ad7a77db82d7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f417.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M33.359 13.254c1.434-3.462 2.06-11.534 1.262-12.004-.704-.416-5.641 1.084-8.34 3.205C23.883 2.909 21.085 2 18 2c-3.085 0-5.883.91-8.281 2.455C7.02 2.334 2.082.834 1.378 1.25c-.797.47-.171 8.543 1.263 12.004C1.59 15.818 1 18.623 1 21.479 1 31.468 2 36 18 36s17-4.532 17-14.521c0-2.856-.59-5.662-1.641-8.225z"/><path fill="#662113" d="M32.878 12.157c.929-3.252 1.081-7.708.524-8.037-.411-.243-2.78.633-5.009 1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f418.svg b/browser/components/torpreferences/content/bridgemoji/1f418.svg new file mode 100644 index 000000000000..fb9656cd18f0 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f418.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M34.453 15.573c-.864-7.3-5.729-10.447-13.93-10.447-.391 0-.763.017-1.139.031-.013-.01-.022-.021-.035-.031C14.655 1.605 4.091 2.779 1.745 6.3c-3.255 4.883-1.174 22.3 0 24.646 1.173 2.35 4.694 3.521 5.868 2.35 1.174-1.176 0-1.176-1.173-3.521-.85-1.701-.466-5.859.255-8.471.028.168.068.322.1.486.39 2.871 1.993 7.412 1.993 9.744 0 3.564 2.102 4.107 4.694 4.107 2.593 0 4.695-.543 4.695-4.107 0-.24-.008-.463-.01 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f419.svg b/browser/components/torpreferences/content/bridgemoji/1f419.svg new file mode 100644 index 000000000000..d915a8fd2e6b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f419.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#553788" d="M10 12c3 5 0 10.692-3 9.692s-4 2-1 3 9.465-.465 13-4c1-1 2-1 2-1L10 12z"/><path fill="#553788" d="M26 12c-3 5 0 10.692 3 9.692s4 2 1 3-9.465-.465-13-4c-1-1-2-1-2-1L26 12z"/><path fill="#744EAA" d="M30.188 16c-3 5 0 10.692 3 9.692s4 2 1 3-9.465-.465-13-4c-1-1-2-1-2-1l11-7.692zM5.812 16c3 5 0 10.692-3 9.692s-4 2-1 3 9.465-.465 13-4c1-1 2-1 2-1L5.812 16z"/><path fill="#9266CC" d="M33.188 31.375c-2.729.91-6.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f41a.svg b/browser/components/torpreferences/content/bridgemoji/1f41a.svg new file mode 100644 index 000000000000..804ece8811a3 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f41a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M29.049 7.033c-.07-.07-.141-.126-.211-.188-1.08-1.087-2.497-1.564-4.008-1.373l-3.812.483c-.382-3.003-2.25-4.446-4.351-4.181l-4.24.536C12.075.767 11.011.034 9.827.185L5.033.79C3.71.958 2.773 2.438 2.94 4.097l.606 6.007c.152 1.501 1.425 2.591 2.403 2.693l.514 5.093c.258 2.553 2.509 4.365 4.117 4.38l.498 4.926c.164 1.622.928 3.027 1.917 4.063.23.339.471.656.737.922 4.423 4.423 12.183 5.064 16.607.64 4.42-4.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f41b.svg b/browser/components/torpreferences/content/bridgemoji/1f41b.svg new file mode 100644 index 000000000000..f8986dd7818b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f41b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AA8DD8" d="M7.415 4c1-1 .25-4-1.438-4s-1.562 2-.562 2 1 2 1 3 1-1 1-1zM3.232 5.72c1-1 .25-4-1.438-4s-1.562 2-.562 2 1 2 1 3 1-1 1-1z"/><path fill="#744EAA" d="M29.607 32.856c.189.808 1.227 2.28 2.032 2.091.806-.19 1.077-1.971.888-2.777-.189-.808-.998-1.307-1.804-1.117-.805.19-1.306.997-1.116 1.803zm-5.434.649c.003.83.681 2.498 1.509 2.495.828-.004 1.494-1.677 1.491-2.506-.003-.829-.677-1.497-1.505-1.495-.828.004-1.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f41d.svg b/browser/components/torpreferences/content/bridgemoji/1f41d.svg new file mode 100644 index 000000000000..31e78828044a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f41d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M27.816 23.464c.299-1.148.465-2.318.465-3.464 0-4.161-2.122-6.779-5.258-8.035.417-1.008.665-2.108.665-3.2 0-1.581-.495-2.498-1.315-3.032 1.091-.482 2.517-1.5 3.027-2.011.781-.781.94-1.889.354-2.475-.586-.586-1.693-.428-2.475.354-.611.611-1.948 2.53-2.223 3.619C20.172 5.025 19.126 5 18 5c-1.125 0-2.172.025-3.055.219-.275-1.089-1.612-3.007-2.223-3.619-.781-.781-1.889-.94-2.475-.354-.585.587-.427 1.694.354 2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f41e.svg b/browser/components/torpreferences/content/bridgemoji/1f41e.svg new file mode 100644 index 000000000000..f314ca9a208e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f41e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M35 21H1c-.552 0-1-.447-1-1s.448-1 1-1h34c.553 0 1 .447 1 1s-.447 1-1 1zm-22.177-2l-.069-.002c-.092-.007-9.214-.714-10.734-8.235-.109-.542.241-1.069.782-1.178.543-.113 1.069.241 1.178.782 1.221 6.044 8.833 6.631 8.91 6.636.551.038.967.515.93 1.066-.036.527-.476.931-.997.931zM3 31c-.142 0-.286-.03-.423-.094-.5-.234-.716-.829-.482-1.33 3.166-6.77 11.038-7.721 11.372-7.758.548-.056 1.042.334 1.103.882.062.548-.332 1.043-.8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f41f.svg b/browser/components/torpreferences/content/bridgemoji/1f41f.svg new file mode 100644 index 000000000000..7d9ef4108b41 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f41f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M32.153 24c0-1 1.523-6.212 3.047-7.735 1.522-1.523 0-3.166-1.523-3.166-3.405 0-9.139 6.901-9.139 10.901 0 5 5.733 10.424 9.139 10.424 1.523 0 3.046-1.404 1.523-2.928C33.677 29.974 32.153 26 32.153 24z"/><path fill="#3B88C3" d="M9.021 14.384c0-3.046 1.497-6.093 3.02-6.093 4.569 0 13.322 4.823 14.845 12.439 1.524 7.616-17.865-6.346-17.865-6.346zm4.854 18.278c1.523 1.523 4.57 3.047 7.617 3.047 3.046 0-3.111- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f420.svg b/browser/components/torpreferences/content/bridgemoji/1f420.svg new file mode 100644 index 000000000000..ccc11c48d024 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f420.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M8.231 10c6-8 14-10 18-10 1 0 1 1 0 1s-6 2-7 9-11 0-11 0zm-1 18c4 3 13 8 18 8 3 0 2-1 1-1s-9-3-10-7-9 0-9 0z"/><path fill="#FFCC4D" d="M35 9c-1.611 0-4.059 2.972-5.268 6.294-1.475-5.62-6.166-10.739-12.945-10.739-8.273 0-12.061 10.216-14.524 14.479-.629 1.091-4.091 1.034-.989 3.103-3.103 2.068.449 2.243 1.222 3.103 2.625 2.92 8.997 6.205 14.291 6.205 5.655 0 11.12-3.786 12.863-8.958C30.828 25.896 33.353 29 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f422.svg b/browser/components/torpreferences/content/bridgemoji/1f422.svg new file mode 100644 index 000000000000..fe4b659ee0fc --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f422.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M9.842 19.922c0 9.842 6.575 9.673 5.158 10.078-7 2-8.803-7.618-9.464-7.618-2.378 0-5.536-.423-5.536-2.46C0 17.883 2.46 15 6.151 15c2.379 0 3.691 2.883 3.691 4.922zM36 28.638c0 1.104-3.518-.741-5 0-2 1-2-.896-2-2s1.343-1 3-1 4 1.895 4 3z"/><path fill="#77B255" d="M16.715 33.143c0 2.761-1.279 2.857-2.857 2.857S11 35.903 11 33.143c0-.489.085-1.029.234-1.587.69-2.59 2.754-5.556 4.052-5.556 1.578 0 1.429 4.382 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f425.svg b/browser/components/torpreferences/content/bridgemoji/1f425.svg new file mode 100644 index 000000000000..bcbd2181bb0a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f425.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M17.38 33.098c-.097-.18-.25-.302-.417-.391C15.366 31.001 16.5 29 16.5 29c0-.553 1-2 0-2l-1 1c-1 1-1 4-1 4h-2c-.553 0-1 .447-1 1s.447 1 1 1h1.108l-.222.12c-.486.263-.667.869-.404 1.355s.869.667 1.356.404l2.639-1.427c.485-.262.666-.868.403-1.354zM23.5 32h-2s0-3-1-4l-1-1c-1 0 0 1.447 0 2 0 0 1.135 2.001-.462 3.707-.168.089-.32.211-.418.391-.263.486-.082 1.093.404 1.355l2.639 1.427c.486.263 1.093.082 1.356-.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f426.svg b/browser/components/torpreferences/content/bridgemoji/1f426.svg new file mode 100644 index 000000000000..06d93a9162da --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f426.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M8.916 12.88c-.111 1.652 1.768 3.126-.712 2.959-2.48-.167-7.836-2.533-7.768-3.53s3.708-2.757 6.188-2.59c2.48.166 2.404 1.508 2.292 3.161zm20.122 16.049c-.202-.032-.392.015-.564.095-2.325.232-3.225-1.885-3.225-1.885-.439-.336-.981-2.009-1.589-1.215l.187 1.402c.187 1.402 2.57 3.224 2.57 3.224l-1.215 1.589c-.336.439-.252 1.066.187 1.402.439.336 1.066.252 1.402-.187l.673-.88-.039.249c-.087.546.285 1.058.831 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f428.svg b/browser/components/torpreferences/content/bridgemoji/1f428.svg new file mode 100644 index 000000000000..1da7190a6813 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f428.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M36 13.533C36 8.867 32.866 7 29 7c-1.621 0-3.285.354-4.676 1.027C22.523 6.798 20.405 6.062 18 6.062s-4.523.736-6.324 1.965C10.285 7.354 8.621 7 7 7c-3.866 0-7 1.867-7 6.533 0 3.844 2.128 6.417 5.038 7.206-.043.536-.068 1.073-.068 1.611 0 5.729 4.52 7.675 8.581 8.326C14.649 31.487 16.232 32 18 32s3.351-.513 4.449-1.323c4.062-.651 8.581-2.597 8.581-8.326 0-.538-.025-1.075-.068-1.611 2.91-.79 5.038-3.363 5.0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f42a.svg b/browser/components/torpreferences/content/bridgemoji/1f42a.svg new file mode 100644 index 000000000000..278e144ffd3d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f42a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M31.595 15.007c-1.75-3.623-5.934-9.053-9.531-9.053-4.071 0-8.228 7.259-10.071 10.378-.176.299-.321.578-.464.857-3.371-1.182-.536-6.631-.536-10.463 0-.957-.138-1.637-.44-2.119.489-.606.586-1.347.192-1.699-.413-.367-1.195-.163-1.745.456-.08.089-.129.186-.189.28-.424-.067-.903-.102-1.472-.102-.565 0-2.916.266-4.229.791C-.007 5.582.993 9 1.993 9h4c1 0 .756 2.31 0 4.726-.83 2.654-1.439 5.145-1 6.606.808 2.687 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f42c.svg b/browser/components/torpreferences/content/bridgemoji/1f42c.svg new file mode 100644 index 000000000000..ee782f2f20e2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f42c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#4292E0" d="M30.584 7.854c.27-1.729 1.028-3.908 2.975-5.854.704-.704.25-2-1-2 0 0-6.061.007-9.893 3.327C21.663 3.115 20.625 3 19.559 3c-8 0-12 4-14 12-.444 1.778-.865 1.399-3 3-1.195.896-2.117 3 1 3 3 0 5 .954 9 1 3.629.042 9.504-3.229 11.087-1.292 2.211 2.706 1.396 5.438.597 6.666-2.904 3.396-5.939.541-8.685-.374-3-1-1 1 0 2s1.312 4 0 6 3 0 5-3c.011-.017.022-.028.032-.045C28.392 31.5 34.559 25.936 34.559 18c0-3.918- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f42d.svg b/browser/components/torpreferences/content/bridgemoji/1f42d.svg new file mode 100644 index 000000000000..91e126242bda --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f42d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#99AAB5" cx="8.5" cy="9.5" r="8.5"/><circle fill="#99AAB5" cx="27.5" cy="9.5" r="8.5"/><path fill="#F4ABBA" d="M13.812 10.031c0 3.228-2.617 5.844-5.844 5.844s-5.844-2.616-5.844-5.844c0-3.227 2.617-5.844 5.844-5.844s5.844 2.617 5.844 5.844zm20.063 0c0 3.228-2.617 5.844-5.844 5.844-3.227 0-5.844-2.616-5.844-5.844 0-3.227 2.617-5.844 5.844-5.844 3.227.001 5.844 2.617 5.844 5.844z"/><path fill="#99AAB5" d="M30 18c0 7.1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f42e.svg b/browser/components/torpreferences/content/bridgemoji/1f42e.svg new file mode 100644 index 000000000000..40fede032f58 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f42e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M4 8s-4 2-4 11c0 0 6-1 7-3 0 0 2-12.25-3-8zm27.995.043s4 2 4 11c0 0-6-.999-7-2.999 0 0-2-12.251 3-8.001z"/><path fill="#FFE8B6" d="M1 1c-1.01.99 1 8 5 9s4-5 3-5C5 5 3.042-1 1 1zm34.297 0c1.011.99-1 8-5 9s-4-5-3-5c4 0 5.959-6 8-4z"/><path fill="#CCD6DD" d="M21.976 31h-7.951C8.488 31 4 26.512 4 20.976v-8.951C4 6.488 8.488 2 14.025 2h7.951C27.512 2 32 6.488 32 12.025v8.951C32 26.512 27.512 31 21.976 31z"/><p [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f42f.svg b/browser/components/torpreferences/content/bridgemoji/1f42f.svg new file mode 100644 index 000000000000..5ecd980c5130 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f42f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#FFCC4D" cx="7" cy="6" r="6"/><circle fill="#FFCC4D" cx="18" cy="30" r="6"/><circle fill="#DD2E44" cx="18" cy="30" r="4"/><circle fill="#FFCC4D" cx="29" cy="6" r="6"/><circle fill="#E6AAAA" cx="7" cy="6" r="4"/><circle fill="#E6AAAA" cx="29" cy="6" r="4"/><path fill="#FFCC4D" d="M34 22c0 7-4.923 7-4.923 7H6.923S2 29 2 22C2 22 3.231 0 18 0c14.77 0 16 22 16 22z"/><path fill="#272B2B" d="M11 17s0-2 2-2 2 2 2 2v2s0 2-2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f430.svg b/browser/components/torpreferences/content/bridgemoji/1f430.svg new file mode 100644 index 000000000000..2f70f9669ff7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f430.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M33.799.005c-.467-.178-7.998 3.971-9.969 9.131-1.166 3.052-1.686 6.058-1.652 8.112C20.709 16.459 19.257 16 18 16s-2.709.458-4.178 1.249c.033-2.055-.486-5.061-1.652-8.112C10.2 3.977 2.668-.173 2.201.005c-.455.174 4.268 16.044 7.025 20.838C6.805 23.405 5 26.661 5 29.828c0 3.234 1.635 5.14 4 5.94 2.531.857 5-.94 9-.94s6.469 1.798 9 .94c2.365-.801 4-2.706 4-5.94 0-3.166-1.805-6.423-4.225-8.984C29.53 16.049 34 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f431.svg b/browser/components/torpreferences/content/bridgemoji/1f431.svg new file mode 100644 index 000000000000..cc75dcc68f5a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f431.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M32.348 13.999s3.445-8.812 1.651-11.998c-.604-1.073-8 1.998-10.723 5.442 0 0-2.586-.86-5.276-.86s-5.276.86-5.276.86C10.001 3.999 2.605.928 2.001 2.001.207 5.187 3.652 13.999 3.652 13.999c-.897 1.722-1.233 4.345-1.555 7.16-.354 3.086.35 5.546.658 6.089.35.617 2.123 2.605 4.484 4.306 3.587 2.583 8.967 3.445 10.761 3.445s7.174-.861 10.761-3.445c2.361-1.701 4.134-3.689 4.484-4.306.308-.543 1.012-3.003.659-6.0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f432.svg b/browser/components/torpreferences/content/bridgemoji/1f432.svg new file mode 100644 index 000000000000..1c1b4347daa1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f432.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M35.125 13.344c0-1 .771-2.327.771-2.635 0-.656-1.553-.421-1.626-1.046-.209-1.794 1.887-3.318-1.745-3.312-1.352.002-.274-1.768-.274-2.725 0-.957-2.596-.145-3.552-.145-.957 0-.957-2.87-1.913-2.87-2.87 0-3.827 2.87-4.783 2.87-.957 0-1.744-3.621-2.87-2.87-2.87 1.913-3.826 7.653-3.826 7.653s4.783-3.826 10.522-2.87c5.345.891 4.79 10.821 5.641 16.888L24.609 36h3.359c.344-1.5 1.939-.529 2.375-1.688.381-1.016-.67- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f433.svg b/browser/components/torpreferences/content/bridgemoji/1f433.svg new file mode 100644 index 000000000000..f00ea1033e02 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f433.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M32 12c2.122-2.122 5 14.312-3 21-2.757 2.305-6 2.969-8 2.969-2.371 0-10.029.031-13 .031-7.18 0-8-4.925-8-11s.82-11 8-11c6 0 10 2 13 3.996 4.977 3.312 5.992 3.861 8 2.004 3.213-2.97 1-6 3-8z"/><path fill="#55ACEE" d="M34.003 23c-.465 3.727-2.041 7.523-5.003 10-2.757 2.305-6 2.969-8 2.969-2.371 0-10.029.031-13 .031-4.035 0-6.062-1.555-7.062-3.996C.157 30.102 4 33 15 33c14 0 17-5 19.003-10z"/><path fill="#3B [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f434.svg b/browser/components/torpreferences/content/bridgemoji/1f434.svg new file mode 100644 index 000000000000..9aa7d69356ae --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f434.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M23.283 23.275s1.374 1.635 2.733 10.047c.143.883.201 1.775.217 2.678H36V7.448C31.613 3.975 25.601 3.259 18.322 5.69c0 0-5.408-3-6.147-3.739-.719-.72-1.857-1.556-1.235.35.364 1.112.764 2.373 2.358 4.862-3.436 2.036-4.513 4.68-8.558 13.341C1.652 27.12.08 29.269.937 31.797c1.13 3.337 5.316 5.595 8.844 3.021 1.919-1.4 2.246-3.913 6.225-6.223 3.653-.065 7.277-1.604 7.277-5.32z"/><path fill="#292F33" d="M36 6.0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f435.svg b/browser/components/torpreferences/content/bridgemoji/1f435.svg new file mode 100644 index 000000000000..ee6c57cf9e41 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f435.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse transform="rotate(-14.999 5.05 17.456)" fill="#D79E84" cx="5.05" cy="17.455" rx="3.818" ry="5.455"/><ellipse transform="rotate(-75.001 31.05 17.455)" fill="#D79E84" cx="31.05" cy="17.455" rx="5.455" ry="3.818"/><path fill="#BF6952" d="M19.018 36h-2.036C10.264 36 3.75 30.848 3.75 23.636c0-4.121 1.527-6.182 1.527-6.182s-.509-2.061-.509-4.121C4.768 7.152 11.282 2 18 2c6.718 0 13.232 6.182 13.232 11.333 0 2.061-.509 4.121-. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f436.svg b/browser/components/torpreferences/content/bridgemoji/1f436.svg new file mode 100644 index 000000000000..8b2e685804d9 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f436.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M15 27v6s0 3 3 3 3-3 3-3v-6h-6z"/><path fill="#BE1931" d="M15 33l.001.037c1.041-.035 2.016-.274 2.632-1.286.171-.281.563-.281.735 0 .616 1.011 1.591 1.251 2.632 1.286V27h-6v6z"/><path fill="#D99E82" d="M31.954 21.619c0 6.276-5 6.276-5 6.276h-18s-5 0-5-6.276c0-6.724 5-18.619 14-18.619s14 12.895 14 18.619z"/><path fill="#F4C7B5" d="M18 20c-7 0-10 3.527-10 6.395 0 3.037 2.462 5.5 5.5 5.5 1.605 0 3.042-.664 4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f437.svg b/browser/components/torpreferences/content/bridgemoji/1f437.svg new file mode 100644 index 000000000000..49175ea42aa2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f437.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4ABBA" d="M34.193 13.329c.387-.371.733-.795 1.019-1.28 1.686-2.854.27-10.292-.592-10.8-.695-.411-5.529 1.05-8.246 3.132C23.876 2.884 21.031 2 18 2c-3.021 0-5.856.879-8.349 2.367C6.93 2.293 2.119.839 1.424 1.249c-.861.508-2.276 7.947-.592 10.8.278.471.615.884.989 1.249C.666 15.85 0 18.64 0 21.479 0 31.468 8.011 34 18 34s18-2.532 18-12.521c0-2.828-.66-5.606-1.807-8.15z"/><path fill="#EA596E" d="M7.398 5.965c-2.166-1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f43a.svg b/browser/components/torpreferences/content/bridgemoji/1f43a.svg new file mode 100644 index 000000000000..af402057ef41 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f43a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M14.858 9.497c.475 2.326-.182 4.236-2.921 4.638-2.741.403-6.7 3.898-8.848-1.798C1.844 9.038 1.092 2.234 2.628 2.009c1.537-.226 11.756 5.162 12.23 7.488z"/><path fill="#CCD6DD" d="M12.784 9.851c.865 1.392-2.205 3.833-3.844 4.568-1.639.736-2.915-.66-4.173-4.1-.55-1.503-1.234-5.532-.634-5.802.599-.268 7.785 3.942 8.651 5.334z"/><path fill="#66757F" d="M21.372 9.497c-.458 2.326.176 4.236 2.818 4.638 2.644.403 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f43b.svg b/browser/components/torpreferences/content/bridgemoji/1f43b.svg new file mode 100644 index 000000000000..50224417b0ce --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f43b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#C1694F" cx="7" cy="6" r="6"/><circle fill="#C1694F" cx="29" cy="6" r="6"/><circle fill="#E6AAAA" cx="7" cy="6" r="4"/><circle fill="#E6AAAA" cx="29" cy="6" r="4"/><path fill="#C1694F" d="M35 22S33.692 0 18 0 1 22 1 22c0 5.872 4.499 10.323 12.216 11.61C14.311 35.06 16.044 36 18 36s3.688-.94 4.784-2.39C30.501 32.323 35 27.872 35 22z"/><circle fill="#DD2E44" cx="18" cy="30" r="4"/><path fill="#D99E82" d="M18 20S7 23. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f43f.svg b/browser/components/torpreferences/content/bridgemoji/1f43f.svg new file mode 100644 index 000000000000..367687490170 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f43f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AD743D" d="M35.432 22.773c-.195.858-.638 1.773-1.022 2.159 1.399-4.418 1.399-9.111-2.25-11.167.112 1.107-.11 1.691-.265 2.153-.21-2.219-.578-3.744-2.158-4.927-1.82-1.363-2.611-.452-.736 3.765 2.2 4.945 1.475 8.603.827 11.216-.038.154-.08.29-.12.439.804-5.765-.989-11.722-6.825-14.915-2.989-1.636-5.211-1.852-5.329-3.037-.135-1.377-1.218-3.698-3.811-5.327.444-1.309-.485-2.787-1.117-2.841-.631-.054-2.024 1.039-2.16 2.52 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f441.svg b/browser/components/torpreferences/content/bridgemoji/1f441.svg new file mode 100644 index 000000000000..bd1a45e4e6e4 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f441.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M35.059 18c0 3.304-7.642 11-17.067 11C8.566 29 .925 22.249.925 18c0-3.314 34.134-3.314 34.134 0z"/><path fill="#292F33" d="M35.059 18H.925c0-3.313 7.642-11 17.067-11s17.067 7.686 17.067 11z"/><path fill="#F5F8FA" d="M33.817 18c0 2.904-7.087 9.667-15.826 9.667-8.74 0-15.825-5.935-15.825-9.667 0-2.912 7.085-9.666 15.825-9.666C26.73 8.333 33.817 15.088 33.817 18z"/><circle fill="#8B5E3C" cx="18" cy="18" r="8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f451.svg b/browser/components/torpreferences/content/bridgemoji/1f451.svg new file mode 100644 index 000000000000..4db8d2bc357e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f451.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M14.174 17.075L6.75 7.594l-3.722 9.481z"/><path fill="#F4900C" d="M17.938 5.534l-6.563 12.389H24.5z"/><path fill="#F4900C" d="M21.826 17.075l7.424-9.481 3.722 9.481z"/><path fill="#FFCC4D" d="M28.669 15.19L23.887 3.523l-5.88 11.668-.007.003-.007-.004-5.88-11.668L7.331 15.19C4.197 10.833 1.28 8.042 1.28 8.042S3 20.75 3 33h30c0-12.25 1.72-24.958 1.72-24.958s-2.917 2.791-6.051 7.148z"/><circle fill="#5C913B" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f455.svg b/browser/components/torpreferences/content/bridgemoji/1f455.svg new file mode 100644 index 000000000000..1db482028323 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f455.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M11 2C8 2 0 4 0 6s1 7 2 9 8 0 9-1 2-12 0-12z"/><path fill="#55ACEE" d="M1 4.717C.352 5.088 0 5.565 0 6c0 2 1 7 2 9 .281.562 1.039.777 2 .85C3 13 2 9 1 4.717z"/><path fill="#3B88C3" d="M25 2c3 0 11 2 11 4s-1 7-2 9-8 0-9-1-2-12 0-12z"/><path fill="#55ACEE" d="M35 4.717c.648.371 1 .848 1 1.283 0 2-1 7-2 9-.281.562-1.039.777-2 .85C33 13 34 9 35 4.717z"/><path fill="#3B88C3" d="M25 2h-1.068C23.515 3.695 21.021 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f457.svg b/browser/components/torpreferences/content/bridgemoji/1f457.svg new file mode 100644 index 000000000000..29cc45c742cf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f457.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M22.42 11.847L26 2s-.398.002-1 .034V0h-2v2.219c-2.271.302-5 1.028-5 2.781 0-1.753-2.729-2.479-5-2.781V0h-2v2.034C10.398 2.002 10 2 10 2l3.581 9.847L1 25.643S7 36 18 36s17-10.357 17-10.357L22.42 11.847z"/><path fill="#BBDDF5" d="M13 11h10v2H13z"/><path fill="#3B88C3" d="M15 13s-6.734 8.106-5.051 9.006C11.633 22.907 15 13 15 13zm6.096 0s6.734 8.105 5.051 9.007c-1.684.9-5.051-9.007-5.051-9.007zM18 13s-2 11 0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f45f.svg b/browser/components/torpreferences/content/bridgemoji/1f45f.svg new file mode 100644 index 000000000000..f0c998c63135 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f45f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8899A6" d="M24.161 10.166l-.676-2.034c-.134-.302-.49-.43-.781-.275-.918.487-2.944 1.318-3.684 1.575 1.419.505 3.499 1.503 4.511 1.396.384-.04.787-.308.63-.662zm10.21 24.574c-.399.225-6.509 1.692-18.621-8.906C12.083 22.625 1.2 12.879 1.341 12.547c0 0-.329.36-.602.736-.197.271-.319.559-.274.848.31 1.967 3.112 3.819 6.962 6.781l.549.422.363.279c.588.452 2.316 1.815 4.329 3.403 2.753 2.171 8.284 6.49 8.445 6.61 2.12 1.5 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f47d.svg b/browser/components/torpreferences/content/bridgemoji/1f47d.svg new file mode 100644 index 000000000000..f6e98d1c1403 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f47d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M35 17c0 9.389-13.223 19-17 19-3.778 0-17-9.611-17-19S8.611 0 18 0s17 7.611 17 17z"/><path fill="#292F33" d="M13.503 14.845c3.124 3.124 4.39 6.923 2.828 8.485-1.562 1.562-5.361.297-8.485-2.828-3.125-3.124-4.391-6.923-2.828-8.485s5.361-.296 8.485 2.828zm8.994 0c-3.124 3.124-4.39 6.923-2.828 8.485 1.562 1.562 5.361.297 8.485-2.828 3.125-3.125 4.391-6.923 2.828-8.485-1.562-1.562-5.361-.297-8.485 2.828zM18 31 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f484.svg b/browser/components/torpreferences/content/bridgemoji/1f484.svg new file mode 100644 index 000000000000..af7e86169328 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f484.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EA596E" d="M16.522.232c2.246 0 4.832 1.375 6.703 3.249C25.098 5.355 27 7.408 27 9.479v17.204l-15.974.031V9.582c-.093-1.506 1.075-9.35 5.496-9.35"/><path fill="#DD2E44" d="M19 11.378V28c0 1.104.896 0 2 0v2c1.104 0 2-.896 2-2V12.333c-1.258-.104-2.65-.262-4-.955z"/><path fill="#F4ABBA" d="M22.072 3.455c2.664 2.391 4.277 5.236 2.961 6.699-1.314 1.463-5.06.991-7.721-1.4-2.661-2.39-3.753-5.516-2.439-6.979 1.316-1.463 4.53 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f488.svg b/browser/components/torpreferences/content/bridgemoji/1f488.svg new file mode 100644 index 000000000000..33ed332874dd --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f488.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#CCD6DD" cx="18" cy="6" r="6"/><path fill="#FFF" d="M11 12h14v21H11z"/><path fill="#DD2E44" d="M11 28.487L20.251 33H25v-2.134l-14-6.83z"/><path fill="#55ACEE" d="M11 19.585l14 6.83v-4.45l-14-6.831z"/><path fill="#DD2E44" d="M13.697 12L25 17.514V12z"/><path fill="#99AAB5" d="M27 11c0 1.104-.896 2-2 2H11c-1.104 0-2-.896-2-2s.896-2 2-2h14c1.104 0 2 .896 2 2zm0 23c0 1.104-.896 2-2 2H11c-1.104 0-2-.896-2-2s.896-2 2-2h14 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f48d.svg b/browser/components/torpreferences/content/bridgemoji/1f48d.svg new file mode 100644 index 000000000000..0834165960a7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f48d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#9AAAB4" d="M18 12c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8z"/><path fill="#5DADEC" d="M29 5l-4-5H11L7 5l11 9z"/><path fill="#8CCAF7" d="M29 5l-4-5H11L7 5h11z"/><path fill="#5DADEC" d="M29 5l-4-5h-7v5h1z"/><path fill="#8CCAF7" d="M18 5h11l-11 9z"/><path fill="#9AAAB4" d="M25 13c0 1.657-1.343 3-3 3h-8c-1.657 0-3-1.343-3-3s1.343- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f48e.svg b/browser/components/torpreferences/content/bridgemoji/1f48e.svg new file mode 100644 index 000000000000..a379f7195f97 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f48e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BDDDF4" d="M13 3H7l-7 9h10z"/><path fill="#5DADEC" d="M36 12l-7-9h-6l3 9z"/><path fill="#4289C1" d="M26 12h10L18 33z"/><path fill="#8CCAF7" d="M10 12H0l18 21zm3-9l-3 9h16l-3-9z"/><path fill="#5DADEC" d="M18 33l-8-21h16z"/></svg> \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f490.svg b/browser/components/torpreferences/content/bridgemoji/1f490.svg new file mode 100644 index 000000000000..f080ef7f1593 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f490.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M35.01 30.967c-1.385-2.226-3.34-5.001-5.265-7.888-5.141-8.772-1.513-18.411-2.893-21.511-.56-1.259-3.879.966-5.555 4.563-.356-.106-.747-.156-1.17-.148.155-2.397.293-4.31-.193-5.403-.71-1.599-5.867 2.418-6.463 7.729-1.848 1.006-3.223 1.957-3.223 1.957s-.95 1.375-1.954 3.223c-5.312.594-9.331 5.753-7.732 6.464 1.097.488 3.015.349 5.421.192-.006.42.046.807.154 1.159-3.61 1.674-5.848 5.006-4.586 5.567 3.282 1.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4a1.svg b/browser/components/torpreferences/content/bridgemoji/1f4a1.svg new file mode 100644 index 000000000000..88b62e387aea --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4a1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M29 11.06c0 6.439-5 7.439-5 13.44 0 3.098-3.123 3.359-5.5 3.359-2.053 0-6.586-.779-6.586-3.361C11.914 18.5 7 17.5 7 11.06 7 5.029 12.285.14 18.083.14 23.883.14 29 5.029 29 11.06z"/><path fill="#CCD6DD" d="M22.167 32.5c0 .828-2.234 2.5-4.167 2.5-1.933 0-4.167-1.672-4.167-2.5 0-.828 2.233-.5 4.167-.5 1.933 0 4.167-.328 4.167.5z"/><path fill="#FFCC4D" d="M22.707 10.293c-.391-.391-1.023-.391-1.414 0L18 13.586 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4a7.svg b/browser/components/torpreferences/content/bridgemoji/1f4a7.svg new file mode 100644 index 000000000000..3116ec31793c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4a7.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5DADEC" d="M28.344 17.768L18.148 1.09 8.7 17.654c-2.2 3.51-2.392 8.074-.081 11.854 3.285 5.373 10.363 7.098 15.811 3.857 5.446-3.24 7.199-10.22 3.914-15.597z"/></svg> \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4b3.svg b/browser/components/torpreferences/content/bridgemoji/1f4b3.svg new file mode 100644 index 000000000000..97641af46a3e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4b3.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M4 5C1.791 5 0 6.791 0 9v18c0 2.209 1.791 4 4 4h28c2.209 0 4-1.791 4-4V9s0-4-4-4H4z"/><path fill="#292F33" d="M0 10h36v5H0z"/><path fill="#F4F7F9" d="M4 19h28v6H4z"/><path fill="#8899A6" d="M19 24c-1.703 0-2.341-1.21-2.469-1.801-.547.041-1.08.303-1.805.764C13.961 23.449 13.094 24 12 24c-1.197 0-1.924-.675-2-2-.003-.056.038-.188.021-.188-1.858 0-3.202 1.761-3.215 1.779-.195.267-.499.409-.806.409-.206 0-.41 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4bf.svg b/browser/components/torpreferences/content/bridgemoji/1f4bf.svg new file mode 100644 index 000000000000..6824a06417d6 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4bf.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8899A6" d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18zm-18-3c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><path fill="#CCD6DD" d="M13.288 17.476c.122-1.104.598-2.101 1.343-2.846L6.686 6.686C4.182 9.19 2.51 12.521 2.102 16.233l11.186 1.243zm9.425 1.048c-.122 1.104-.598 2.101-1.343 2.846l7.944 7.944c2.504-2.504 4.176-5.835 4.584-9.547l-11.185-1.243z"/><path fill="#F5F8FA" d= [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4cc.svg b/browser/components/torpreferences/content/bridgemoji/1f4cc.svg new file mode 100644 index 000000000000..2ab6da3e1472 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4cc.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BE1931" d="M23.651 23.297L12.702 12.348l9.386-7.821 9.385 9.385z"/><path fill="#DD2E44" d="M34.6 13.912c-1.727 1.729-4.528 1.729-6.255 0l-6.257-6.256c-1.729-1.727-1.729-4.53 0-6.258 1.726-1.727 4.528-1.727 6.257 0L34.6 7.656c1.728 1.727 1.728 4.529 0 6.256z"/><path fill="#99AAB5" d="M14 17.823S-.593 35.029.188 35.813C.97 36.596 18.177 22 18.177 22L14 17.823z"/><path fill="#DD2E44" d="M25.215 27.991c-1.726 1.729-4.52 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4ce.svg b/browser/components/torpreferences/content/bridgemoji/1f4ce.svg new file mode 100644 index 000000000000..284cf6674b65 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4ce.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M35.354 25.254c.217-2.391-.513-4.558-2.057-6.102L17.033 2.89c-.391-.391-1.024-.391-1.414 0-.391.391-.391 1.024 0 1.414l16.264 16.263c1.116 1.117 1.642 2.717 1.479 4.506-.159 1.748-.957 3.456-2.188 4.686-1.23 1.23-2.938 2.027-4.685 2.187-1.781.161-3.39-.362-4.506-1.479L3.598 12.082c-.98-.98-1.059-2.204-.953-3.058.15-1.196.755-2.401 1.66-3.307 1.7-1.7 4.616-2.453 6.364-.707l14.85 14.849c1.119 1.12.026 2.803 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4d5.svg b/browser/components/torpreferences/content/bridgemoji/1f4d5.svg new file mode 100644 index 000000000000..701ff017c504 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4d5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M35 26c0 2.209-1.791 4-4 4H5c-2.209 0-4-1.791-4-4V6.313C1 4.104 6.791 0 9 0h20.625C32.719 0 35 2.312 35 5.375V26z"/><path fill="#CCD6DD" d="M33 30c0 2.209-1.791 4-4 4H7c-2.209 0-4-1.791-4-4V6c0-4.119-.021-4 5-4h21c2.209 0 4 1.791 4 4v24z"/><path fill="#E1E8ED" d="M31 31c0 1.657-1.343 3-3 3H4c-1.657 0-3-1.343-3-3V7c0-1.657 1.343-3 3-3h24c1.657 0 3 1.343 3 3v24z"/><path fill="#BE1931" d="M31 32c0 2.209-1.79 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4e1.svg b/browser/components/torpreferences/content/bridgemoji/1f4e1.svg new file mode 100644 index 000000000000..dcb02946d303 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4e1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M10.746 21.521c1.668 0 7.43 4.345 7.427 9.701.003 5.358-14.853 5.358-14.854-.001.001-5.356 5.759-9.7 7.427-9.7z"/><path fill="#CCD6DD" d="M8.541 25.182c8.839 8.84 17.337 5.163 20.033 2.469 2.695-2.696-.158-9.916-6.371-16.129C15.988 5.308 8.767 2.455 6.072 5.15 3.377 7.845-.299 16.343 8.541 25.182z"/><path fill="#66757F" d="M12.443 21.278c6.214 6.214 13.434 9.066 16.13 6.372 2.695-2.696-.158-9.916-6.371-16 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4e2.svg b/browser/components/torpreferences/content/bridgemoji/1f4e2.svg new file mode 100644 index 000000000000..8bd0c94fb602 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4e2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BE1931" d="M12.908 30.75c-.276 2.209-2.291 3-4.5 3s-3.776-1.791-3.5-4l1-9c.276-2.209 2.291-4 4.5-4s6.468 0 3.5 4-1 10-1 10z"/><path fill="#CCD6DD" d="M35.825 14.75c0 6.902-1.544 12.5-3.45 12.5-1.905 0-20.45-5.598-20.45-12.5 0-6.903 18.545-12.5 20.45-12.5 1.906 0 3.45 5.597 3.45 12.5z"/><ellipse fill="#66757F" cx="32.375" cy="14.75" rx="3.45" ry="12.5"/><path fill="#DD2E44" d="M17.925 21.75l-14-1c-5 0-5-12 0-12l14-1c [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f4fb.svg b/browser/components/torpreferences/content/bridgemoji/1f4fb.svg new file mode 100644 index 000000000000..33808ad72ba8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f4fb.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M2.697 12.389c-.391.391-.391 1.023 0 1.414s1.023.391 1.414 0l9.192-9.192c.391-.391.391-1.023 0-1.414s-1.023-.391-1.414 0l-9.192 9.192z"/><path fill="#99AAB5" d="M36 32c0 4-4 4-4 4H4s-4 0-4-4V14s0-4 4-4h28c4 0 4 4 4 4v18z"/><path fill="#292F33" d="M15.561 3.061c-.391.391-1.023.391-1.414 0l-.707-.707c-.391-.391-.391-1.023 0-1.414s1.023-.391 1.414 0l.707.707c.39.39.39 1.023 0 1.414z"/><circle fill="#292F33" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f50b.svg b/browser/components/torpreferences/content/bridgemoji/1f50b.svg new file mode 100644 index 000000000000..66d420fc301e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f50b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M24 4c0 2.209-1.791 4-4 4h-4c-2.209 0-4-1.791-4-4s1.791-4 4-4h4c2.209 0 4 1.791 4 4z"/><path fill="#E1E8ED" d="M30 32c0 2.209-1.791 4-4 4H10c-2.209 0-4-1.791-4-4V8c0-2.209 1.791-4 4-4h16c2.209 0 4 1.791 4 4v24z"/><path fill="#77B255" d="M6 8h24v24H6z"/><path fill="#F5F8FA" d="M23 14h-3v-3c0-.552-.447-1-1-1h-2c-.552 0-1 .448-1 1v3h-3c-.553 0-1 .448-1 1v2c0 .553.447 1 1 1h3v3c0 .553.448 1 1 1h2c.553 0 1-.44 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f511.svg b/browser/components/torpreferences/content/bridgemoji/1f511.svg new file mode 100644 index 000000000000..7b4dc2a73dc7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f511.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M32.614 3.414C28.31-.89 21.332-.89 17.027 3.414c-3.391 3.392-4.098 8.439-2.144 12.535l-3.916 3.915c-.64.641-.841 1.543-.625 2.359l-1.973 1.972c-.479-.48-1.252-.48-1.731 0l-1.731 1.732c-.479.479-.479 1.253 0 1.732l-.867.864c-.479-.478-1.253-.478-1.731 0l-.866.867c-.479.479-.479 1.253 0 1.732.015.016.036.02.051.033-.794 1.189-.668 2.812.382 3.863 1.195 1.195 3.134 1.195 4.329 0L20.08 21.144c4.097 1.955 9.14 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f525.svg b/browser/components/torpreferences/content/bridgemoji/1f525.svg new file mode 100644 index 000000000000..e7dee6dd270b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f525.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M35 19c0-2.062-.367-4.039-1.04-5.868-.46 5.389-3.333 8.157-6.335 6.868-2.812-1.208-.917-5.917-.777-8.164.236-3.809-.012-8.169-6.931-11.794 2.875 5.5.333 8.917-2.333 9.125-2.958.231-5.667-2.542-4.667-7.042-3.238 2.386-3.332 6.402-2.333 9 1.042 2.708-.042 4.958-2.583 5.208-2.84.28-4.418-3.041-2.963-8.333C2.52 10.965 1 14.805 1 19c0 9.389 7.611 17 17 17s17-7.611 17-17z"/><path fill="#FFCC4D" d="M28.394 23.99 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f526.svg b/browser/components/torpreferences/content/bridgemoji/1f526.svg new file mode 100644 index 000000000000..1aff8ae2c0d7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f526.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M23 17l1-1s1-1 2 0l2 2s1 1 0 2l-1 1-4-4z"/><path fill="#8899A6" d="M34.879 27.879c1.166 1.166 1.166 3.076 0 4.242l-2.758 2.758c-1.166 1.166-3.076 1.166-4.242 0L9.121 16.121c-1.167-1.166-1.167-3.076 0-4.242l2.758-2.758c1.167-1.167 3.076-1.167 4.242 0l18.758 18.758z"/><path fill="#66757F" d="M20.879 10.879c1.166 1.167 1.166 3.076 0 4.242l-5.758 5.758c-1.167 1.166-3.076 1.166-4.242 0L6 16s-1-1 0-2l8-8c1-1 2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f52c.svg b/browser/components/torpreferences/content/bridgemoji/1f52c.svg new file mode 100644 index 000000000000..e910ee894e4a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f52c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="#66757F"><path d="M19.78 21.345l-6.341-6.342-.389 4.38 2.35 2.351z"/><path d="M15.4 22.233c-.132 0-.259-.053-.354-.146l-2.351-2.351c-.104-.104-.158-.25-.145-.397l.389-4.38c.017-.193.145-.359.327-.425.182-.067.388-.021.524.116l6.341 6.342c.138.138.183.342.116.524s-.232.31-.426.327l-4.379.389-.042.001zm-1.832-3.039l2.021 2.021 3.081-.273-4.828-4.828-.274 3.08z"/></g><path fill="#8899A6" d="M31 32h-3c0-3.314-2.63-6-5.875-6 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f52d.svg b/browser/components/torpreferences/content/bridgemoji/1f52d.svg new file mode 100644 index 000000000000..113c0330a8f3 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f52d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#9AAAB4" cx="17" cy="20" r="3"/><path fill="#DA2F47" d="M19.235 17.059c-.259.485-.868.671-1.353.412L5.529 10.883c-.485-.259-.67-.868-.412-1.353L7.94 4.235c.259-.485.868-.67 1.353-.412l12.353 6.588c.485.259.671.868.412 1.353l-2.823 5.295z"/><path fill="#DA2F47" d="M31.177 22.293c-.259.485-.868.671-1.354.412l-9.705-5.176c-.485-.259-.671-.868-.412-1.354l1.882-3.529c.259-.485.868-.67 1.354-.412l9.705 5.176c.485.259.671 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f52e.svg b/browser/components/torpreferences/content/bridgemoji/1f52e.svg new file mode 100644 index 000000000000..d7e09232d7e8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f52e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BF6952" d="M28.736 28.767l-21.458.076-1.649 4.835c-.062.187-.101.387-.073.582.145.984.993 1.74 2.017 1.74h20.894c1.024 0 1.872-.756 2.017-1.74.029-.195-.01-.395-.073-.582l-1.675-4.911z"/><path fill="#854836" d="M28.736 28.767l-.19-.556c-.199-.598-.759-1.001-1.389-1.001H8.883c-.63 0-1.19.403-1.389 1.001l-.216.632c2.928 2.344 6.636 3.754 10.678 3.754 4.088 0 7.837-1.438 10.78-3.83z"/><circle fill="#AA8DD8" cx="17.956" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f54a.svg b/browser/components/torpreferences/content/bridgemoji/1f54a.svg new file mode 100644 index 000000000000..613e4956d71d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f54a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M.794 16.112c1 0 5.875 1.344 6.5 2.312L6.013 18.3s-3.906-1.25-4.906-1.25c-1.001 0-.313-.938-.313-.938z"/><path fill="#99AAB5" d="M11 2c1 1 3 7 3 10s-2 6 2 8 5.001-1 5.001-5S20 7 19 5s-2 0-2 0-1-4-2-4-1 2-1 2-2-2-3-2 0 1 0 1z"/><path fill="#CCD6DD" d="M10 15c3 0 4 4 6 4s1-3 3-3 10-5 10-11 3-3 3-1c1 0 2 1.586 2 3 0 1 0 2-1 3 1 0 2 2 1 3 1 3-1 6-3 7 0 1-2 3-4 2 0 0-1 3-3 2 0 0 3.052 1.684 4 2 3 1 7 1 7 1s0 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f58c.svg b/browser/components/torpreferences/content/bridgemoji/1f58c.svg new file mode 100644 index 000000000000..b39bcbc9dd7e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f58c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M14.57 27.673c2.814-1.692 6.635-3.807 9.899-7.071 7.03-7.029 12.729-16.97 11.314-18.385C34.369.803 24.428 6.502 17.398 13.531c-3.265 3.265-5.379 7.085-7.071 9.899l4.243 4.243z"/><path fill="#C1694F" d="M.428 34.744s7.071 1.414 12.021-3.536c2.121-2.121 2.121-4.949 2.121-4.949l-2.829-2.829s-3.535.708-4.95 2.122c-1.414 1.414-2.518 4.232-2.888 5.598-.676 2.502-3.475 3.594-3.475 3.594z"/><path fill="#CCD6DD" d [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f58d.svg b/browser/components/torpreferences/content/bridgemoji/1f58d.svg new file mode 100644 index 000000000000..5a4cbe8e1a8c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f58d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M35.702 7.477L28.522.298c-.396-.397-1.038-.397-1.436 0L3.395 23.989c-.397.397-.397 1.038 0 1.437.72.719-3.14 7.959-3.395 8.496L2.068 36c.536-.255 7.785-4.115 8.506-3.395.397.397 1.039.397 1.436 0L35.702 8.913c.397-.396.397-1.039 0-1.436z"/><path fill="#EA596E" d="M4.139 23.24L26.407.972l8.62 8.62L12.759 31.86z"/><path fill="#292F33" d="M23.534 3.846l1.437-1.436 8.62 8.62-1.437 1.436zM5.576 21.803l1.436-1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f5ff.svg b/browser/components/torpreferences/content/bridgemoji/1f5ff.svg new file mode 100644 index 000000000000..5ba18be1f934 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f5ff.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M8 27s-2 4-2 6 4 3 12 3 12-1 12-3-2-6-2-6H8zM9 6C6 6 6 7 5 16c-.994 8.945 0 10 4 10 2 0 2-20 0-20zm18 0c3 0 3 1 4 10 .994 8.945 0 10-4 10-2 0-2-20 0-20z"/><path fill="#CCD6DD" d="M8 3s1-3 10-3 10 3 10 3 1 6 1 14-1 14-1 14-1 2-10 2-10-2-10-2-1-3-1-14S8 3 8 3z"/><path fill="#66757F" d="M28 8c0-1-3-4-10-4S8 7 8 8c0 .807 4.548 1.612 6.311 1.894C14.999 12.548 15 18 15 18c.805-3.218.315-7.079.093-8.467C15.295 8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f680.svg b/browser/components/torpreferences/content/bridgemoji/1f680.svg new file mode 100644 index 000000000000..8658d4397b8c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f680.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M1 17l8-7 16 1 1 16-7 8s.001-5.999-6-12-12-6-12-6z"/><path fill="#FFAC33" d="M.973 35s-.036-7.979 2.985-11S15 21.187 15 21.187 14.999 29 11.999 32c-3 3-11.026 3-11.026 3z"/><circle fill="#FFCC4D" cx="8.999" cy="27" r="4"/><path fill="#55ACEE" d="M35.999 0s-10 0-22 10c-6 5-6 14-4 16s11 2 16-4c10-12 10-22 10-22z"/><path d="M26.999 5c-1.623 0-3.013.971-3.641 2.36.502-.227 1.055-.36 1.641-.36 2.209 0 4 1.791 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f681.svg b/browser/components/torpreferences/content/bridgemoji/1f681.svg new file mode 100644 index 000000000000..8b7a40320cb3 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f681.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M16.26 26h2v5h-2zm-8 0h2v5h-2z"/><ellipse fill="#99AAB5" cx="6.259" cy="3" rx="6" ry="1"/><ellipse fill="#99AAB5" cx="20.259" cy="3" rx="6" ry="1"/><path fill="#99AAB5" d="M12.26 3h2v6h-2z"/><ellipse fill="#66757F" cx="13.259" cy="3" rx="2" ry="1"/><path fill="#FFCC4D" d="M34.259 10c0-3 0-7-1-7s-3 4-4 6 5 1 5 1z"/><path fill="#FFCC4D" d="M34.259 10c0-2.209-8-3-19-3h-2C6.632 7 .509 12.451.509 18.25S4.259 2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f686.svg b/browser/components/torpreferences/content/bridgemoji/1f686.svg new file mode 100644 index 000000000000..3f5f5b85617c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f686.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A7A9AC" d="M2 36h32L23 19H13z"/><path fill="#58595B" d="M5 36h26L21 19h-6z"/><path fill="#808285" d="M8 36h20l-9-17h-2z"/><path fill="#A7A9AC" d="M28 35c0 .553-.447 1-1 1H9c-.552 0-1-.447-1-1 0-.553.448-1 1-1h18c.553 0 1 .447 1 1zm-2-4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1 0-.553.448-1 1-1h14c.553 0 1 .447 1 1z"/><path fill="#58595B" d="M27.076 25.3L23 19H13l-4.076 6.3c1.889 2.517 4.798 4.699 9.076 4.699 4.277 0 7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f68b.svg b/browser/components/torpreferences/content/bridgemoji/1f68b.svg new file mode 100644 index 000000000000..da204b81a3b0 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f68b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#939598" d="M0 34h36v2H0z"/><path fill="#58595B" d="M10 32c0-1.657-1.344-3-3-3s-3 1.343-3 3 1.343 3 3 3 3-1.343 3-3z"/><path fill="#292F33" d="M9 32c0-1.105-.896-2-2-2s-2 .895-2 2 .896 2 2 2 2-.895 2-2z"/><circle fill="#58595B" cx="29" cy="32" r="3"/><circle fill="#292F33" cx="29" cy="32" r="2"/><path fill="#E6E7E8" d="M34 2H2c-.552 0-1-.448-1-1s.448-1 1-1h32c.553 0 1 .448 1 1s-.447 1-1 1z"/><path fill="#66757F" d="M [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f68d.svg b/browser/components/torpreferences/content/bridgemoji/1f68d.svg new file mode 100644 index 000000000000..68ca65faa073 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f68d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M11 34c0 1.104-.896 2-2 2H7c-1.104 0-2-.896-2-2v-7c0-1.104.896-2 2-2h2c1.104 0 2 .896 2 2v7zm20 0c0 1.104-.896 2-2 2h-2c-1.104 0-2-.896-2-2v-7c0-1.104.896-2 2-2h2c1.104 0 2 .896 2 2v7z"/><path fill="#99AAB5" d="M3 17c0 .553-.448 1-1 1H1c-.552 0-1-.447-1-1v-7c0-.552.448-1 1-1h1c.552 0 1 .448 1 1v7zm33 0c0 .553-.447 1-1 1h-1c-.553 0-1-.447-1-1v-7c0-.552.447-1 1-1h1c.553 0 1 .448 1 1v7z"/><path fill="#99AAB5 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f695.svg b/browser/components/torpreferences/content/bridgemoji/1f695.svg new file mode 100644 index 000000000000..5446414a2544 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f695.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M21.377 15.449c.089.816-.83 1.495-2.053 1.515-1.223.02-2.095-.635-1.947-1.463l.356-2c.147-.829.938-1.5 1.767-1.5.828 0 1.572.662 1.661 1.478l.216 1.97z"/><path fill="#FFCC4D" d="M32.246 21h-.135l-.444-3c-.327-2.209-1.864-4-3.433-4H16.162c-1.569 0-3.574 1.791-4.478 4l-1.228 3H6.911c-2.073 0-4.104 1.791-4.538 4l-.588 3c-.001.008 0 .015-.002.021C.782 28.132 0 28.97 0 30c0 1.104.896 2 2 2h30c2.209 0 4-1.791 4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f697.svg b/browser/components/torpreferences/content/bridgemoji/1f697.svg new file mode 100644 index 000000000000..97b5f10d4c5a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f697.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M13 32h20s3 0 3-4c0-2 0-6-1-7s-8-7-11-7h-6c-3 0-10 7-10 7l-4 1s-3 1-3 3v3s-1 .338-1 1.957C0 32 2 32 2 32h11z"/><path fill="#BBDDF5" d="M20 16h-2c-2 0-8 6-8 6s4.997-.263 10-.519V16zm10 3c-1-1-5-3-7-3h-1v5.379c4.011-.204 7.582-.379 8-.379 1 0 1-1 0-2z"/><circle fill="#292F33" cx="10" cy="31" r="4"/><circle fill="#CCD6DD" cx="10" cy="31" r="2"/><circle fill="#292F33" cx="27" cy="31" r="4"/><circle fill="#CCD [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f69a.svg b/browser/components/torpreferences/content/bridgemoji/1f69a.svg new file mode 100644 index 000000000000..7441d1bba804 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f69a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M36 27c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4v-3c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v3z"/><path fill="#FFCC4D" d="M19 13l-.979-1H7.146C4 12 3 14 3 14l-3 5.959V25h19V13z"/><path fill="#55ACEE" d="M9 20H2l2-4s1-2 3-2h2v6z"/><circle fill="#292F33" cx="9" cy="31" r="4"/><circle fill="#CCD6DD" cx="9" cy="31" r="2"/><circle fill="#292F33" cx="27" cy="31" r="4"/><circle fill="#CCD6DD" cx="27" cy="31" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f69c.svg b/browser/components/torpreferences/content/bridgemoji/1f69c.svg new file mode 100644 index 000000000000..014ca11d340a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f69c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M11 11h3v9h-3z"/><path fill="#77B255" d="M24 26.157C24 28.832 22.354 31 20.325 31H4.709c-2.029 0-3.488-1.565-3.258-3.494l.625-5.241c.23-1.93 1.992-3.808 3.928-4.199l14.628-3.21C22.496 14.413 24 16.219 24 18.893v7.264z"/><path fill="#292F33" d="M16.535 24.167C16.239 26.283 17.791 28 20 28h9c2.209 0 4-1.717 4-3.833V8.833C33 6.716 31.547 5 29.755 5h-7.303c-1.792 0-3.484 1.716-3.78 3.833l-2.137 15.334z"/><pat [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6a0.svg b/browser/components/torpreferences/content/bridgemoji/1f6a0.svg new file mode 100644 index 000000000000..8510956dc2af --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6a0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#58595B" d="M19 12V5h-2v7h-5v5h12v-5z"/><path fill="#FFD983" d="M34 28V18c0-2.209-1.791-4-4-4H6c-2.209 0-4 1.791-4 4v10h32z"/><path fill="#269" d="M2 22v6c0 4.418 3.582 8 8 8h16c4.418 0 8-3.582 8-8v-6H2z"/><path fill="#88C9F9" d="M2 18h6v9H2zm8 0h7v9h-7zm9 0h7v9h-7zm9 0h6v9h-6z"/><path fill="#6D6E71" d="M33.213 10.977L3.27 2.954c-.533-.144-.85-.691-.707-1.226.143-.533.691-.85 1.225-.707L33.73 9.045c.534.144.851.691.7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6a2.svg b/browser/components/torpreferences/content/bridgemoji/1f6a2.svg new file mode 100644 index 000000000000..79077f00a46c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6a2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M16 0h4v5h-4z"/><path fill="#D1D3D4" d="M16 2h4v4h-4z"/><path fill="#A7A9AC" d="M18 2h2v4h-2z"/><path fill="#66757F" d="M18 19H5s1.355 8 4.875 14C13.394 39 18 33 18 33s4.606 6 8.125 0C29.645 27 31 19 31 19H18z"/><path fill="#99AAB5" d="M18 19H5s1.355 8 4.875 14C13.394 39 18 33 18 33V19z"/><path fill="#55ACEE" d="M0 30h36v6H0z"/><path fill="#E6E7E8" d="M30 9h-2.45l-.242-4H8.692l-.241 4H6v6h2.088l-.242 4h20 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6a4.svg b/browser/components/torpreferences/content/bridgemoji/1f6a4.svg new file mode 100644 index 000000000000..7dfe5a6b81b2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6a4.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M33 27h-2l1-8h2z"/><path fill="#DD2E44" d="M1 14c9.055.053 11 1 17 2 2.959.493 7 1 11 1 2 0 4 1 4 4 0 2 .534 3.187-5.433 3.815C23.59 25.233 13 23 9 21c-6-3-9-7.005-8-7z"/><path fill="#55ACEE" d="M0 24h36v12H0z"/><path fill="#FFCC4D" d="M5 13c4 0 11 0 18 1s10 1 10 3v3c0 1.414-6.869.447-10 0-7-1-14-3-18-4-4.047-1.011-7-3 0-3z"/><path fill="#A0041E" d="M30.592 24.322c2.122-.539 2.403-1.307 2.418-2.361-1.263. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6f0.svg b/browser/components/torpreferences/content/bridgemoji/1f6f0.svg new file mode 100644 index 000000000000..6d9bb3d2eaf1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6f0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M8.514 19.828L19.122 9.223l2.121 2.121L10.635 21.95z"/><path fill="#55ACEE" d="M8.515 29.728c-.781.781-2.047.781-2.828 0l-4.95-4.949c-.781-.781-.781-2.048 0-2.828L5.687 17c.781-.781 2.047-.781 2.828 0l4.95 4.95c.781.78.781 2.047 0 2.828l-4.95 4.95zm16.262-16.263c-.78.781-2.047.781-2.827 0L17 8.515c-.781-.781-.781-2.047 0-2.828l4.951-4.95c.781-.781 2.047-.781 2.828 0l4.949 4.95c.781.781.781 2.047.001 2.828 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6f4.svg b/browser/components/torpreferences/content/bridgemoji/1f6f4.svg new file mode 100644 index 000000000000..e217dc436402 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6f4.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#292F33" cx="4.5" cy="31.5" r="4.5"/><circle fill="#DD2E44" cx="4.5" cy="31.5" r="3"/><circle fill="#292F33" cx="31.5" cy="31.5" r="4.5"/><circle fill="#DD2E44" cx="31.5" cy="31.5" r="3"/><path fill="#55ACEE" d="M31.5 33h-20c-.825 0-1.5-.675-1.5-1.5s.675-1.5 1.5-1.5h20c.825 0 1.5.675 1.5 1.5s-.675 1.5-1.5 1.5z"/><path fill="#55ACEE" d="M26.012 31.746c-.003-.082-.012-.163-.012-.246 0-3.038 2.462-5.5 5.5-5.5 1.21 0 2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6f5.svg b/browser/components/torpreferences/content/bridgemoji/1f6f5.svg new file mode 100644 index 000000000000..a1e0f4b99e20 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6f5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M32 24h-1c-.55 0-1-.45-1-1s.45-1 1-1h1c.55 0 1 .45 1 1s-.45 1-1 1"/><path fill="#292F33" d="M10 31.5c0 2.485-2.015 4.5-4.5 4.5S1 33.985 1 31.5 3.015 27 5.5 27s4.5 2.015 4.5 4.5m24 0c0 2.485-2.015 4.5-4.5 4.5S25 33.985 25 31.5s2.015-4.5 4.5-4.5 4.5 2.015 4.5 4.5"/><path fill="#99AAB5" d="M8 31.5C8 32.881 6.881 34 5.5 34S3 32.881 3 31.5 4.119 29 5.5 29 8 30.119 8 31.5m24 0c0 1.381-1.119 2.5-2.5 2.5S27 32.88 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6f6.svg b/browser/components/torpreferences/content/bridgemoji/1f6f6.svg new file mode 100644 index 000000000000..b6a2165254a3 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6f6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M33.793 17S32.476 20 18 20C3.523 20 1.973 17 1.973 17S-1 22.117 4.802 25c4.238 2.105 10.916-.131 12.723-.814 1.991.683 9.274 2.824 13.557.814 5.862-2.751 2.711-8 2.711-8z"/><path fill="#55ACEE" d="M0 24h36v12H0z"/><path fill="#FFAC33" d="M27.005 25.389c.206 0 .412-.079.569-.236.315-.315.315-.824 0-1.139l-8.861-8.86c-.315-.315-.824-.315-1.139 0-.315.315-.315.824 0 1.139l8.861 8.86c.158.157.364.236.57.236z" [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6f8.svg b/browser/components/torpreferences/content/bridgemoji/1f6f8.svg new file mode 100644 index 000000000000..5f015fe792bb --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6f8.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M32.831 20.425c-.689 3.241-9.21 6.221-17.314 4.499S.841 17.013 1.53 13.772s8.587-3.287 16.69-1.564 15.3 4.976 14.611 8.217z"/><path fill="#FFD983" d="M27 36l-2-14-17-5-8 19z"/><ellipse transform="rotate(-78 17.482 15.686)" fill="#67757F" cx="17.481" cy="15.685" rx="7.556" ry="17"/><path fill="#67757F" d="M.414 10.977l.414 2.315 32.866 6.986 1.412-2.126z"/><ellipse transform="rotate(-78 18.013 13.186)" fil [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6f9.svg b/browser/components/torpreferences/content/bridgemoji/1f6f9.svg new file mode 100644 index 000000000000..1ee4bfec4ac7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6f9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M32.436 2.821c1.801 1.799 3.779 2.737 0 6.517L12.884 28.889c-4.594 4.595-5.463 7.571-11.405 1.63-3.258-3.258.284-3.543 4.888-8.148l19.552-19.55c1.8-1.8 4.718-1.8 6.517 0z"/><path fill="#DD2E44" d="M33.936 4.321c1.801 1.799 1.801 4.717 0 6.517L14.385 30.389c-4.073 4.073-8.342 4.693-11.405 1.63-3.258-3.258.284-3.543 4.888-8.148L27.42 4.321c1.799-1.8 4.717-1.8 6.516 0z"/><path fill="#3B88C3" d="M15.301 33.18 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6fa.svg b/browser/components/torpreferences/content/bridgemoji/1f6fa.svg new file mode 100644 index 000000000000..aae4e94ac5a0 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6fa.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M19 9h2v11h-2z"/><path fill="#55ACEE" d="M10 9c-2 2-4 5-4 7 0 4 5 1 5 1V9h-1z"/><circle fill="#292F33" cx="5" cy="32" r="4"/><circle fill="#99AAB5" cx="5" cy="32" r="2"/><path fill="#1E5200" d="M29 23h-2c-1.105 0-2 .895-2 2v5c0 1.105.895 2 2 2h2c1.105 0 2-.895 2-2v-5c0-1.105-.895-2-2-2zm-10 0h-2c-1.105 0-2 .895-2 2v5c0 1.105.895 2 2 2h2c1.105 0 2-.895 2-2v-5c0-1.105-.895-2-2-2z"/><path fill="#5C913B" d="M [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f6fc.svg b/browser/components/torpreferences/content/bridgemoji/1f6fc.svg new file mode 100644 index 000000000000..091d51ef63eb --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f6fc.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M10 27.5c0 1.381-2.567 3.5-4.5 3.5S1 28.881 1 27.5 3.567 26 5.5 26s4.5.119 4.5 1.5zm19 0c0 1.381-2.567 3.5-4.5 3.5S20 28.881 20 27.5s2.567-1.5 4.5-1.5 4.5.119 4.5 1.5zm0-.5c0 1 0 2 2 3s2-3 2-3h-4z"/><path fill="#F4900C" d="M34.787 28.795c1.332 1.332.729 1.683-.603 3.016-1.332 1.332-1.683 1.935-3.016.603-1.332-1.332-1.935-2.889-.603-4.222 1.332-1.333 2.889-.73 4.222.603z"/><path fill="#662113" d="M33.299 2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f916.svg b/browser/components/torpreferences/content/bridgemoji/1f916.svg new file mode 100644 index 000000000000..1dbe6d68de79 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f916.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#F4900C" cx="33.5" cy="14.5" rx="2.5" ry="3.5"/><ellipse fill="#F4900C" cx="2.5" cy="14.5" rx="2.5" ry="3.5"/><path fill="#FFAC33" d="M34 19c0 .553-.447 1-1 1h-3c-.553 0-1-.447-1-1v-9c0-.552.447-1 1-1h3c.553 0 1 .448 1 1v9zM7 19c0 .553-.448 1-1 1H3c-.552 0-1-.447-1-1v-9c0-.552.448-1 1-1h3c.552 0 1 .448 1 1v9z"/><path fill="#FFCC4D" d="M28 5c0 2.761-4.478 4-10 4C12.477 9 8 7.761 8 5s4.477-5 10-5c5.522 0 10 2.239 10 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f93f.svg b/browser/components/torpreferences/content/bridgemoji/1f93f.svg new file mode 100644 index 000000000000..01239db87846 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f93f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M21 25c-2.556 0-4.062-3.128-5.161-4.676-.412-.58-1.266-.58-1.677 0C13.062 21.872 11.557 25 9 25c-4.551 0-7.923-3.033-8.784-7.619C-.804 11.955 3.589 7 9.11 7h11.78c5.521 0 9.914 4.955 8.894 10.381C28.923 21.967 25.551 25 21 25z"/><path fill="#292F33" d="M18 22l-3-3-3 3-2 2c2 0 3 3 5 3s3-3 5-3l-2-2z"/><path fill="#4E9322" d="M21 25c-2.556 0-4.062-3.128-5.161-4.676-.412-.58-1.266-.58-1.677 0C13.062 21.872 11 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f941.svg b/browser/components/torpreferences/content/bridgemoji/1f941.svg new file mode 100644 index 000000000000..e197d16c3e09 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f941.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F18F26" d="M0 18h36v9H0z"/><ellipse fill="#F18F26" cx="18" cy="26" rx="18" ry="9"/><ellipse fill="#F18F26" cx="18" cy="27" rx="18" ry="9"/><path fill="#9D0522" d="M0 10v16h.117c.996 4.499 8.619 8 17.883 8s16.887-3.501 17.883-8H36V10H0z"/><ellipse fill="#F18F26" cx="18" cy="11" rx="18" ry="9"/><ellipse fill="#F18F26" cx="18" cy="12" rx="18" ry="9"/><path fill="#F18F26" d="M0 10h1v2H0zm35 0h1v2h-1z"/><ellipse fill="#F [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f94c.svg b/browser/components/torpreferences/content/bridgemoji/1f94c.svg new file mode 100644 index 000000000000..9bc8f8a58d42 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f94c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BE1931" d="M28 12H13V8c0-.552.448-1 1-1h11c1.104 0 2-.896 2-2s-.896-2-2-2H14c-2.761 0-5 2.239-5 5v4H8c-1.104 0-2 .896-2 2s.896 2 2 2h20c1.104 0 2-.896 2-2s-.896-2-2-2z"/><path fill="#66757F" d="M36 25c0 3.313-2.687 6-6 6H6c-3.313 0-6-2.687-6-6v-5c0-3.313 2.687-6 6-6h24c3.313 0 6 2.687 6 6v5z"/><path fill="#99AAB5" d="M0 20h36v5H0z"/></svg> \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f94f.svg b/browser/components/torpreferences/content/bridgemoji/1f94f.svg new file mode 100644 index 000000000000..84fdba474f0a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f94f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M35 21.526C35 26.758 27.389 31 18 31S1 26.758 1 21.526 7.625 11 18 11s17 5.294 17 10.526z"/><ellipse fill="#FFAC33" cx="18" cy="19.502" rx="16" ry="8.5"/><path fill="#FFD983" d="M18 11.331c-6.449 0-11.5 2.491-11.5 5.672 0 3.18 5.051 5.671 11.5 5.671 6.448 0 11.5-2.491 11.5-5.671 0-3.181-5.052-5.672-11.5-5.672z"/><ellipse fill="#FFAC33" cx="18" cy="18.002" rx="12" ry="6"/><path fill="#F4900C" d="M29 18.002 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f950.svg b/browser/components/torpreferences/content/bridgemoji/1f950.svg new file mode 100644 index 000000000000..eef4358d88ee --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f950.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M11.569 35.41c-2.223-.63-5.362-4.11-6.86-7.331-.183-.391-1.101-.673-1.265-1.084C.953 20.775.637 12.54 6.342 6.834 12.751.425 20.537-.295 26.817 1.757c.45.147.837 1.114 1.286 1.199 5.917 1.115 8.322 7.549 5.759 6.816-1.199-.342-2.657-.557-3.814-.428-.437.049-1.051.892-1.544.962-.767.11-2.64.212-3.564.305-9.159.916-12.395 6.971-12.811 14.256-.046.808-.617.623-.73 1.283-.344 1.992 1.089 4.884 1.772 6.939.594 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f951.svg b/browser/components/torpreferences/content/bridgemoji/1f951.svg new file mode 100644 index 000000000000..ed1d9f9f633c --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f951.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M35 5.904c2.394 6.042-1.438 20.543-10.5 26.5-9.06 5.957-20.395 3.573-23.097-6.443-1.669-6.186 2.79-10.721 11.851-16.677C22.315 3.327 32.64-.053 35 5.904z"/><path fill="#3E721D" d="M20.605 26.03c-6.523 4.546-15.287 5.15-18.469.582-3.183-4.566.418-12.578 6.943-17.124 6.522-4.545 21.951-9.796 25.134-5.23 3.183 4.57-7.085 17.226-13.608 21.772"/><path fill="#A6D388" d="M19.815 26.578c-5.757 4.013-13.482 3.097- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f955.svg b/browser/components/torpreferences/content/bridgemoji/1f955.svg new file mode 100644 index 000000000000..b949554ff7a1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f955.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M31.096 8.933c3.535-2.122 4.408-8.32 3.701-7.613.707-.707-5.657 0-7.778 3.536 0-1.414-1.414-7.071-3.535-2.121-2.122 4.95-1.415 5.657-1.415 7.071 0 1.414 2.829 1.414 2.829 1.414s-.125 2.704 1.29 2.704c1.414 0 1.997.583 6.946-1.538 4.95-2.122-.624-3.453-2.038-3.453z"/><path fill="#F4900C" d="M22.422 23.594C14.807 31.209 2.27 36.675.502 34.907c-1.768-1.768 3.699-14.305 11.313-21.92 7.615-7.615 11.53-7.562 14 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f956.svg b/browser/components/torpreferences/content/bridgemoji/1f956.svg new file mode 100644 index 000000000000..8da10427ff0f --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f956.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EDB980" d="M12.697 31.165c-3.785 3.785-8.273 3.838-9.841 2.271-1.568-1.568-1.514-6.056 2.271-9.841L24.052 4.67c3.785-3.784 8.271-3.838 9.84-2.271 1.567 1.568 1.515 6.056-2.271 9.841L12.697 31.165z"/><path d="M24.322 7.969c1.568 1.568 2.758 2.917 3.595 2.082.152-.152.241-.349.301-.565-.018-.043-.036-.087-.041-.136-.128-1.148-2.41-3.641-4.08-4.721l-.045.042-1.474 1.474c.438.539 1.064 1.144 1.744 1.824zm-4.543 4.542c1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f95c.svg b/browser/components/torpreferences/content/bridgemoji/1f95c.svg new file mode 100644 index 000000000000..c809689a0536 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f95c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M17.256 31.519c-4.754 4.754-11.207 5.573-14.683 2.097C-.902 30.141.056 23.55 4.672 18.934c3.355-3.356 6.711-3.356 9.228-5.873 2.517-2.516 1.678-5.033 5.454-8.809C23.968-.361 29.84-1.201 33.616 2.574c3.774 3.776 2.832 9.754-1.678 14.263-3.775 3.775-6.292 2.936-8.809 5.452-2.518 2.518-2.518 5.873-5.873 9.23z"/><path fill="#C1694F" d="M8.211 21.397c-.324 0-.643-.157-.835-.448-.305-.46-.179-1.081.282-1.386.68 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f95d.svg b/browser/components/torpreferences/content/bridgemoji/1f95d.svg new file mode 100644 index 000000000000..4007a720a280 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f95d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M25.94 15.188c2.045 8.239-1.642 16.246-8.235 17.881-6.592 1.636-13.593-3.719-15.638-11.958-2.044-8.24 1.642-16.246 8.236-17.882 6.59-1.636 13.593 3.718 15.637 11.959z"/><path fill="#C1694F" d="M34.146 13.151c2.391 9.635-6.295 17.402-14.948 19.548-8.653 2.146-14.34-3.532-16.385-11.773-2.044-8.24.328-15.92 8.98-18.066 8.652-2.148 19.801.005 22.353 10.291z"/><path fill="#77B255" d="M24.448 15.558c1.789 7.211 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f95e.svg b/browser/components/torpreferences/content/bridgemoji/1f95e.svg new file mode 100644 index 000000000000..795fb134113d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f95e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#929497" cx="18" cy="23.949" rx="18" ry="10"/><ellipse fill="#CCD6DC" cx="18" cy="22.199" rx="18" ry="10"/><path fill="#F19020" d="M32 20.393c0 4.295-6.268 7.778-14 7.778S4 24.688 4 20.393s6.268-7.778 14-7.778 14 3.483 14 7.778z"/><path fill="#FAAA35" d="M32 18.239c0 4.295-6.268 7.778-14 7.778S4 22.534 4 18.239s6.268-7.778 14-7.778 14 3.483 14 7.778z"/><path fill="#F19020" d="M32 16.085c0 4.295-6.268 7.778-14 7.77 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f965.svg b/browser/components/torpreferences/content/bridgemoji/1f965.svg new file mode 100644 index 000000000000..7f1692794b5e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f965.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8B5E3C" d="M5.506 30.167c-7.03-7.029-6.397-19.059 1.414-26.87l25.456 25.456c-7.811 7.811-19.841 8.443-26.87 1.414z"/><path fill="#603913" d="M6.92 3.297c-3.905 3.905-1.373 12.77 5.656 19.8 7.031 7.029 15.895 9.561 19.799 5.656 3.905-3.904 1.374-12.769-5.657-19.799C19.69 1.924 10.825-.607 6.92 3.297z"/><path fill="#F5F8FA" d="M31.591 24.217c.229.229.56.336.887.276.387-.071.696-.362.79-.745.067-.269 1.575-6.67-3.013-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f966.svg b/browser/components/torpreferences/content/bridgemoji/1f966.svg new file mode 100644 index 000000000000..ee2bc1cb1092 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f966.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M23.013 24.322l-5.546-5.546-5.547-5.546S12.375 24.413.737 27.323c0 0 1.032 2.487 3.364 4.819s4.819 3.364 4.819 3.364c2.91-11.639 14.093-11.184 14.093-11.184z"/><path fill="#5C913B" d="M13.83 16.594c-.727 3.637-5.092 9.456-5.092 9.456l8.728-8.728c2.909-2.909-3.636-.728-3.636-.728zm12.365 6.546s-10.91.727-17.457 5.819c8.001-8.001 11.638-8.728 11.638-8.728l5.819 2.909z"/><path fill="#3E721D" d="M30.156 28.19 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f968.svg b/browser/components/torpreferences/content/bridgemoji/1f968.svg new file mode 100644 index 000000000000..10bd682b6834 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f968.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M35 16c0-8-6-11-12-11-1.738 0-3.472.371-5 1.097C16.472 5.371 14.738 5 13 5 7 5 1 8 1 16c0 3 1.314 5.662 3 8 .434.602.903 1.146 1.4 1.647-.357.603-.616 1.091-.779 1.353C4 28 5 29 6 29c.69 0 1.205-.617 1.745-1.482C10.552 29.273 14.023 30 18 30s7.448-.727 10.255-2.482C28.795 28.383 29.31 29 30 29c1 0 2-1 1.379-2-.163-.262-.422-.75-.779-1.353.497-.501.967-1.046 1.4-1.647 1.686-2.338 3-5 3-8zm-17 1.207C16.578 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f96c.svg b/browser/components/torpreferences/content/bridgemoji/1f96c.svg new file mode 100644 index 000000000000..6ef36cb221f1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f96c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M29.866 24.625c2.522-.841 5.884-4.203 5.884-5.884s-2.522-.841-1.681-3.362 1.681-5.043 0-6.724-3.362 0-5.043-1.681 0-3.362-1.681-5.043-4.203-.841-6.724 0S18.939.25 17.258.25s-5.043 3.362-5.884 5.884-2.521.84-3.361 3.362c-.78 2.341.177 4.823-1.972 9.748-1.026 1.621-1.995 3.178-2.899 4.543C-.836 29.792.449 35.552.449 35.552s5.76 1.285 11.765-2.693c1.365-.904 2.922-1.873 4.543-2.899 4.925-2.15 7.407-1.192 9.7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f96d.svg b/browser/components/torpreferences/content/bridgemoji/1f96d.svg new file mode 100644 index 000000000000..b5607c107cc4 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f96d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M26.38 3.539c.483-.039 1.259-.655 1.664-.595.405.06 1.573 1.078 1.67 1.283.097.205-.637 1.178-.746 1.48-.109.302-.64.239-1.51-.543-.869-.782-1.078-1.625-1.078-1.625z"/><path fill="#EA564B" d="M12.3 3.139c4.545-2.66 11.267-2.611 13.685-.58 1.617 1.358 2.942 2.401 4.474 3.011 2.022.804 3.692 3.154 4.415 5.384.981 3.023 1.68 12.579-8.029 18.516-6.233 3.812-17.656 5.363-18.961 4.723-.984-.483-4.621-2.09-6.675 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f96f.svg b/browser/components/torpreferences/content/bridgemoji/1f96f.svg new file mode 100644 index 000000000000..6a65f4c19330 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f96f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M18.09 9.295C7.847 9.295.573 13.188.573 18.053c0 7.569 5.839 15.57 17.517 15.57 11.678 0 17.517-8.001 17.517-15.57 0-4.865-7.274-8.758-17.517-8.758z"/><path fill="#D99E82" d="M18.09 8.322C7.847 8.322.573 11.961.573 17.927c0 5.966 7.273 9.858 17.517 9.858 10.243 0 17.517-4.146 17.517-9.858S28.333 8.322 18.09 8.322z"/><path fill="#CCD6DD" d="M2.519 20.973c-.973.973 4.866 6.812 7.785 3.893 2.919-2.919 5.942 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f980.svg b/browser/components/torpreferences/content/bridgemoji/1f980.svg new file mode 100644 index 000000000000..8f45b53dd4f9 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f980.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M6.96 20.637c.068.639-.543 1.228-1.368 1.315-.824.089-1.547-.357-1.615-.995-.068-.639.544-1.227 1.368-1.314.824-.089 1.547.356 1.615.994zm2.087 2.717c.125.818-1.756 2.544-2.576 2.669-.819.125-1.584-.438-1.708-1.257-.125-.818.58-1.14 1.398-1.265.819-.124 2.761-.965 2.886-.147zm1.783 2.104c.173.81-1.628 3.927-2.438 4.1-.811.173-1.645.146-1.817-.665-.173-.81.306-1.688 1.116-1.861.81-.174 2.966-2.384 3.139-1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f981.svg b/browser/components/torpreferences/content/bridgemoji/1f981.svg new file mode 100644 index 000000000000..674ff24e628a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f981.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M32.325 10.958s2.315.024 3.511 1.177c-.336-4.971-2.104-8.249-5.944-10.13-3.141-1.119-6.066 1.453-6.066 1.453s.862-1.99 2.19-2.746C23.789.236 21.146 0 18 0c-3.136 0-5.785.227-8.006.701 1.341.745 2.215 2.758 2.215 2.758S9.194.803 6 2.053C2.221 3.949.481 7.223.158 12.174c1.183-1.19 3.55-1.215 3.55-1.215S-.105 13.267.282 16.614c.387 2.947 1.394 5.967 2.879 8.722C3.039 22.15 5.917 20 5.917 20s-2.492 5.96-.581 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f984.svg b/browser/components/torpreferences/content/bridgemoji/1f984.svg new file mode 100644 index 000000000000..19d9ff1613d2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f984.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1CDD5" d="M36 19.854C33.518 9.923 25.006 1.909 16.031 6.832c0 0-4.522-1.496-5.174-1.948-.635-.44-1.635-.904-.912.436.423.782.875 1.672 2.403 3.317C8 12.958 9.279 18.262 7.743 21.75c-1.304 2.962-2.577 4.733-1.31 6.976 1.317 2.33 4.729 3.462 7.018 1.06 1.244-1.307.471-1.937 3.132-4.202 2.723-.543 4.394-1.791 4.394-4.375 0 0 .795-.382 1.826 6.009.456 2.818-.157 5.632-.039 8.783H36V19.854z"/><path fill="#60379A" d="M31 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f986.svg b/browser/components/torpreferences/content/bridgemoji/1f986.svg new file mode 100644 index 000000000000..085c90063468 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f986.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M12.75 17.75c1.226 2.195 1.856-1.361 9.312-2.625s13.729 4.454 13.859 5.413c.132.958-4.447 9.462-9.462 9.462H10.75c-4.143 0-7.022-7.224-4-11.438 0 0 4.5-3.5 6-.812z"/><path fill="#C1694F" d="M13.008 18.136C8.02 25.073 6.969 30 10.75 30c-4.143 0-6.578-6.188-4.468-11.031.463-1.064 1.758-2.492 1.758-2.492l4.179-.008c.162.32.599 1.365.789 1.667z"/><path fill="#E1E8ED" d="M20.062 22.75c6.672-2.682 15.729-3.171 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f987.svg b/browser/components/torpreferences/content/bridgemoji/1f987.svg new file mode 100644 index 000000000000..4ebb5ad4c15f --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f987.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M23 21c0 6.352-3 10-5 10s-5-3.648-5-10 2.239-7 5-7c2.762 0 5 .648 5 7z"/><circle fill="#66757F" cx="18" cy="11" r="4"/><path fill="#66757F" d="M14 11c-2-5 1-7 1-7s2 1 2 4-3 3-3 3z"/><path fill="#546066" d="M14.668 9.904c-.776-2.457-.119-3.896.403-4.58C15.486 5.773 16 6.608 16 8c0 1.268-.739 1.734-1.332 1.904z"/><path fill="#66757F" d="M22 11c2-5-1-7-1-7s-2 1-2 4 3 3 3 3zm-5.984 3c-1.62 1.157-10 2-9-5 .142 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f988.svg b/browser/components/torpreferences/content/bridgemoji/1f988.svg new file mode 100644 index 000000000000..f6381c521eaf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f988.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M36 21c0 5-3 9-11 11-7.062 1.766-21-.934-21-2 0-1.129 2.503-1.371 6.812-1.125 10.938.625 13.75-3.857 12-5.25-3.062-2.437-6.437.375-12.062-.125C3.782 22.881 0 17.472 0 16c0-2 11.716-8 20-8s16 4.25 16 13z"/><path fill="#292F33" d="M15 12.5c0 .829-.671 1.5-1.5 1.5s-1.5-.671-1.5-1.5.671-.5 1.5-.5 1.5-.329 1.5.5z"/><path fill="#66757F" d="M14 9c2-5 5.291-9 7.958-9S21 2 26 10 14 9 14 9zM3 30c0-4-1.04-6 .146-6s6 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f989.svg b/browser/components/torpreferences/content/bridgemoji/1f989.svg new file mode 100644 index 000000000000..bb0d461ab47a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f989.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M7.317 11c-5.723 9.083.958 18 .958 18s2.874-.442 6.875-5.2c4-4.758-7.833-12.8-7.833-12.8zm21.342 0c5.723 9.083-.958 18-.958 18s-2.874-.442-6.875-5.2C16.825 19.042 28.659 11 28.659 11z"/><path fill="#FFAC33" d="M15.203 31.557c-.123-.229-.317-.384-.531-.496-2.032-2.172-.589-4.717-.589-4.717 0-.703 1.271-2.544 0-2.544l-1.272 1.272c-1.272 1.271-1.272 5.089-1.272 5.089H8.995c-.703 0-1.272.568-1.272 1.272 0 .70 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f98a.svg b/browser/components/torpreferences/content/bridgemoji/1f98a.svg new file mode 100644 index 000000000000..2cb2f986dfa2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f98a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M13.431 9.802c.658 2.638-8.673 10.489-11.244 4.098C.696 10.197-.606 2.434.874 2.065c1.48-.368 11.9 5.098 12.557 7.737z"/><path fill="#A0041E" d="M11.437 10.355c.96 1.538-1.831 4.561-3.368 5.522-1.538.961-2.899-.552-4.414-4.414-.662-1.689-1.666-6.27-1.103-6.622.562-.351 7.924 3.976 8.885 5.514z"/><path fill="#F4900C" d="M22.557 9.802C21.9 12.441 31.23 20.291 33.802 13.9c1.49-3.703 2.792-11.466 1.312-11.835 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f98b.svg b/browser/components/torpreferences/content/bridgemoji/1f98b.svg new file mode 100644 index 000000000000..22c6ead8ffcf --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f98b.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#1C6399" d="M20.004 20.243c-.426 0-.858.01-1.294.031-.436 1.268-.468 2.747 0 5.097.328 1.646 2.659 6.299 4.584 7.933.683.58 1.638.884 2.69.884 2.144 0 4.691-1.265 6.157-4.034 3.001-5.671-3.474-9.911-12.137-9.911z"/><path fill="#1C6399" d="M33.666 1.973c-.204 0-.425.021-.663.066-3.182.601-9.302 5.126-14.287 11.771 0 0-.789 5.16-.789 6.194 0 .336 1.264.5 3.058.5 3.717 0 9.709-.705 11.424-2.041 1.898-1.479 3.65-9.804 3. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f98c.svg b/browser/components/torpreferences/content/bridgemoji/1f98c.svg new file mode 100644 index 000000000000..86623680c243 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f98c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M15.971 15.083c-1.458-2.333-.667-7.708 0-8.958s-.542-2.458-1.5-.458-.996 3-3.162 2.458-1.088-3.292-.379-5.625c.729-2.4-.917-1.959-1.667-.458-.75 1.5-1.254 5.693-2.901 5.984-1.647.291-6.099.599-2.851-5.651C4.818-.139 2.773-.656 1.68 1.459.361 4.007-.404 7.25.221 8.625c1.113 2.448 3.483 2.95 6.983 2.284s6.101-.634 6.101 1.133c0 1.872.208 3.458 1.042 3.625s1.624-.584 1.624-.584zm4.057 0c1.458-2.333.667-7.708 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f98e.svg b/browser/components/torpreferences/content/bridgemoji/1f98e.svg new file mode 100644 index 000000000000..1164a73b64f1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f98e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M15.66 6.844c.485-2.03.384-3.139-.553-3.677-.936-.537.337-2.666 1.977-.793 1.641 1.873 1.428 3.618.662 4.853-.767 1.234-2.336.66-2.086-.383zm-.48 2.951c-.916 1.877-1.699 2.668-2.764 2.487-1.064-.18-1.435 2.272 1.023 1.869 2.457-.403 3.401-1.886 3.595-3.326.193-1.44-1.383-1.994-1.854-1.03zm11.336 6.261c-2.072-1.377-5.382 1.43-5.699 2.28-.317.85 1.721 3.461 1.146 4.94-.577 1.477 1.142 1.768 1.92.491.778-1.2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f98f.svg b/browser/components/torpreferences/content/bridgemoji/1f98f.svg new file mode 100644 index 000000000000..0d07017ab4fd --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f98f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M27.295 7.526c-1.152-3.054-2.65-6.8-8.299-6.915-1.759-.036-1.52.539-.347 1.02 2.248.922-2.474 5.438 4.759 7.4 5.648 1.531 5.039 1.549 3.887-1.505z"/><path fill="#99AAB5" d="M36.004 3.903c-7.737 0-14.411 2.514-18.105 5.757-4.875 4.28-5.38 10.306-14.034 15.66-2.847 1.761-.423 7.517 1.153 9.618 1.908 2.544 28.617.238 30.987-8.777l-.001-22.258z"/><path fill="#66757F" d="M8.676 29.652c1.011.507 1.787 1.051 1.3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f992.svg b/browser/components/torpreferences/content/bridgemoji/1f992.svg new file mode 100644 index 000000000000..233e3c989dc0 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f992.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M22.217 35.615h-.002c-.804-.142-1.34-.908-1.198-1.712l.59-3.344s.68-3.165-1.076-5.449c1.259-1.171 5.024.085 5.024.085l-1.626 9.221c-.142.804-.908 1.34-1.712 1.199zm-12.692-.021h-.002c-.804-.142-1.34-.908-1.198-1.712l1.312-7.025s.503-2.419.503-4.439c1.142-.439 2.723 2.757 2.723 2.757l-1.626 9.221c-.143.803-.909 1.339-1.712 1.198zm21.277-9.823c-.537 0-.977-.431-.985-.969-.012-.723-.304-4.326-4.925-4.326-.545 0-.985-.441-. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f993.svg b/browser/components/torpreferences/content/bridgemoji/1f993.svg new file mode 100644 index 000000000000..2cb4b9eb2e8b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f993.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M23.283 23.275s1.374 1.635 2.733 10.047c.143.883.201 1.775.217 2.678H36V7.448C31.613 3.975 25.601 3.259 18.322 5.69c0 0-5.408-3-6.147-3.739-.719-.72-1.857-1.556-1.235.35.364 1.112.764 2.373 2.358 4.862-3.436 2.036-4.513 4.68-8.558 13.341C1.652 27.12.08 29.269.937 31.797c1.13 3.337 5.316 5.595 8.844 3.021 1.919-1.4 2.246-3.913 6.225-6.223 3.653-.065 7.277-1.604 7.277-5.32z"/><path fill="#292F33" d="M36 6.0 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f994.svg b/browser/components/torpreferences/content/bridgemoji/1f994.svg new file mode 100644 index 000000000000..ebbfc2ad25cd --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f994.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#6D6E71" d="M28.688 20.312C28.688 26.217 23.904 31 18 31c-5.903 0-10.688-4.783-10.688-10.688 0-5.903 4.786-10.689 10.688-10.689 5.904.001 10.688 4.786 10.688 10.689z"/><path fill="#662113" d="M26 33.5H10c-1.665 0-2.479-1.339-2.763-2.31l-2.664-.056c-.153-.003-.297-.077-.389-.2-.092-.122-.123-.281-.083-.43l.594-2.216-2.446-.651c-.152-.041-.276-.15-.335-.296s-.046-.311.035-.445l1.199-1.993-2.156-1.192c-.139-.077-.233-.2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f995.svg b/browser/components/torpreferences/content/bridgemoji/1f995.svg new file mode 100644 index 000000000000..fb046c69c7bc --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f995.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M17.68 22s1.937 7.873 1.187 9.373c-2.373 4.746-1.187 4.627 0 4.627h2.373c2.373 0-1.187-14-1.187-14H17.68zM5.255 19.387s-.436 7.993-1.187 9.493C1.695 33.626 1.695 36 2.882 36h2.373c1.186 0 1.187-4.154 1.187-5.34 0-1.187 1.187-8.603 1.187-8.603l-2.374-2.67z"/><path fill="#77B255" d="M12.374 5.251c-2.068.017-4.209-.435-5.523 2.099-1.404 2.709-2.065 8.579.776 9.664 2.479.947 7.933-1.187 13.866 1.187 5.933 2.3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f998.svg b/browser/components/torpreferences/content/bridgemoji/1f998.svg new file mode 100644 index 000000000000..8a72b40a7af2 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f998.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BF6952" d="M6.513.886c0 .984.649 3.926 1.153 5.047s-.505.35-1.442-.771-1.657-2.244-1.225-3.435S6.513-.446 6.513.886z"/><path fill="#D99E82" d="M.314 10.699c-.004.961 1.22 2.097 2.883 1.682 1.333-.332 3.234-1.359 3.316-.561.082.798-1.179 3.471-.629 6.276.93 4.74 2.379 8.873 7.201 10.095 0 0 .723 2.775 1.479 3.986.694 1.111 1.029 2.022.45 2.215-.579.193-3.472.386-4.694.579C9.099 35.164 8.906 36 10.256 36H17.2c.707 0 . [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f999.svg b/browser/components/torpreferences/content/bridgemoji/1f999.svg new file mode 100644 index 000000000000..b505faf82d7a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f999.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M6.755 2.246c-.443-.368-1.05-.976-1.629-1.299-.497-.277-.247.918.173 1.391.421.473 1.824 1.342 2.455 1.815.631.473.936.398.315-.473-.649-.911-1.314-1.434-1.314-1.434z"/><path fill="#D99E82" d="M8.191.736c.328.339.735 2.394.735 2.394s1.282.092 2.407.786c4.5 2.776 2.542 9.542 3.944 11.102.432.48 9.681-1.643 14.222.544 3.844 1.852 3.083 4.646 4.083 5.271.758.474-2 1.25-2.578-2.313-.506 11.147-1.072 13.867-1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f99a.svg b/browser/components/torpreferences/content/bridgemoji/1f99a.svg new file mode 100644 index 000000000000..7606d382ac63 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f99a.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M14.5 36c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707L16 33.293V31.5c0-.276.224-.5.5-.5s.5.224.5.5v2.207l-2.146 2.146c-.098.098-.226.147-.354.147z"/><path fill="#292F33" d="M16.5 34h-3c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h3c.276 0 .5.224.5.5s-.224.5-.5.5zm5 2c-.128 0-.256-.049-.354-.146L19 33.707V31.5c0-.276.224-.5.5-.5s.5.224.5.5v1.793l1.854 1.854c.195.195.195.512 0 .707-.098.097-.226.146-.354.14 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f99c.svg b/browser/components/torpreferences/content/bridgemoji/1f99c.svg new file mode 100644 index 000000000000..f7f743c52258 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f99c.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M17.5 35c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707L19 32.293V29.5c0-.276.224-.5.5-.5s.5.224.5.5v3.207l-2.146 2.146c-.098.098-.226.147-.354.147z"/><path fill="#66757F" d="M19.5 33h-3c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h3c.276 0 .5.224.5.5s-.224.5-.5.5zm3 2c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707L24 32.293V29.5c0-.276.224-.5.5-.5s.5.224.5.5v3.207l-2.146 2.146c-.098.098-.226.147-.3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f99d.svg b/browser/components/torpreferences/content/bridgemoji/1f99d.svg new file mode 100644 index 000000000000..30c88720fb80 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f99d.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M10.668 5.616C9.032 2.479 4.439 1.005 3.584 2.298s.707 10.152 1.757 9.967c1.051-.185 5.327-6.649 5.327-6.649z"/><path fill="#292F33" d="M9.841 7.674c-1.27-2.68-4.696-4.82-5.304-3.745-.435.771.122 5.263.757 7.696l4.547-3.951z"/><path fill="#99AAB5" d="M25.284 5.616c1.636-3.137 6.228-4.611 7.084-3.318s-.708 10.152-1.758 9.967c-1.051-.185-5.326-6.649-5.326-6.649z"/><path fill="#292F33" d="M26.36 7.674c1.231- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f99e.svg b/browser/components/torpreferences/content/bridgemoji/1f99e.svg new file mode 100644 index 000000000000..8df2a7467606 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f99e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="#BE1931"><path d="M14.847 22.402h-.064c-2.117-.069-4.919-1.006-6.246-5.077-.135-.413.089-.864.503-.999.414-.135.857.085.992.499l.003.011c.312.959 1.263 3.876 4.8 3.991.435.015.776.378.762.812-.014.415-.344.743-.75.763zm-5.348 5.637c.151-.221.411-.359.698-.342 3.034.181 4.578-1.938 5.086-2.634.256-.352.749-.429 1.1-.173.352.256.414.747.173 1.1-1.698 2.33-3.869 3.434-6.453 3.28-.434-.026-.766-.399-.74-.834.01-.148.059-.28 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9a3.svg b/browser/components/torpreferences/content/bridgemoji/1f9a3.svg new file mode 100644 index 000000000000..1aa87190b9ea --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9a3.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M9.396 20.496s2.053 6.29 2.053 8.144.023 3.271-.374 4.463c-.397 1.192-.066 1.523 1.523 1.589 1.589.066 3.774 0 3.973-1.192.199-1.192-.156-1.55-.089-2.742.066-1.192.331-4.37.53-4.701.199-.331 3.906.662 4.635.927s3.046.265 3.112 1.059c.066.795.487 4.86.288 5.655-.199.795.397.993 2.251.927 1.986-.071 3.112-.463 2.979-1.324-.132-.861-.222-2.146.043-3.139s1.258-3.84 1.324-4.767c0 0 .927-2.053.861-3.575-.066-1. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9a4.svg b/browser/components/torpreferences/content/bridgemoji/1f9a4.svg new file mode 100644 index 000000000000..1dbac1e3177b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9a4.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E08110" d="M17.338 26.109c0 1.515-.032 2.456-.771 2.813-.739.357-3.354.065-3.805.339-.45.274-.845.792-.42.78.425-.012 1.004.13 1.004.13s-.821.222-1.127.531-.464.961-.148.923c.316-.038 1.869-.427 2.177-.429 0 0-.386.482-.429.816-.043.335.229.478.229.478s1.118-.495 1.551-.697c.433-.202 1.161-.83 1.504-.977.343-.147.768-.158 2.126.249 0 0 .016-.655-.51-.859-.526-.205-.915-.058-.802-.847.134-.936.436-2.834.713-3.342.278 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9a5.svg b/browser/components/torpreferences/content/bridgemoji/1f9a5.svg new file mode 100644 index 000000000000..7371a8ed19f1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9a5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#7A5C44" d="M6 21c-1.105 0-2-.895-2-2V4c0-1.105.895-1 2-1s2-.105 2 1c0 0-.606 6.424 0 11.997C8.54 20.971 12 22 12 22c0 1.105-4.895-1-6-1zm14 4.999c-1.105 0-2-.895-2-2v-21c0-1.105.895-1 2-1s2-.105 2 1c0 0-.446 5.108-.125 9.297.32 4.187.125 11.703.125 11.703 0 1.104-.895 2-2 2z"/><path fill="#662113" d="M36 4L0 8V4l36-2z"/><path fill="#A78E81" d="M22.644 18.406c-.633 1.126-1.363 1.809-2.16 2.193-.402.041-1.132-.085-1.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9a6.svg b/browser/components/torpreferences/content/bridgemoji/1f9a6.svg new file mode 100644 index 000000000000..5ea0173a5008 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9a6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A78E81" d="M2.381 8.671c-1.573 0-2.359.786-1.573 1.573 1.573 1.573 3.145 1.573 3.931.786L2.381 8.671zm6.29-6.29c0-1.573.786-2.359 1.573-1.573 1.573 1.573 1.573 3.145.786 3.931L8.671 2.381zm-4.717 9.435s8.547 14.433 14.939 16.512c3.344 1.087 5.692.599 7.863-1.573s2.406-4.447 1.573-7.863C26.937 13.188 11.816 3.954 11.816 3.954l-7.862 7.862z"/><path fill="#A78E81" d="M16.129 26.852c4.231 5.36 8.793 7.807 10.465 6.519 1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9a7.svg b/browser/components/torpreferences/content/bridgemoji/1f9a7.svg new file mode 100644 index 000000000000..038284558534 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9a7.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E26000" d="M36 32c0-12-2-28-18-28S0 20 0 32h8v-5c0-1 1-1 1 0 0 7 4 6 4 6l2 2 1-1c0-.552 1.448-1 2-1s2 .448 2 1l1 1 2-2s4 1 4-6c0-1 1-1 1 0v5h8z"/><circle fill="#E26000" cx="18" cy="13.5" r="11.5"/><path fill="#D9B981" d="M32 31c-3 0-4 1-4 1v1c0 .552.448 1 1 1s1-.448 1-1c0 .552.448 1 1 1s1-.448 1-1c0 .552.448 1 1 1s1-.448 1-1c0 .552.448 1 1 1s1-.448 1-1v-1s-1-1-4-1zM4 31c-3 0-4 1-4 1v1c0 .552.448 1 1 1s1-.448 1-1c0 . [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9a9.svg b/browser/components/torpreferences/content/bridgemoji/1f9a9.svg new file mode 100644 index 000000000000..aaa5cfa26d38 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9a9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EA596E" d="M22.903 27.55c1.218-.812 2.232-.783 3.073-.058-.435-1.131-2.348-3.624-2.348-3.624l-.725 3.682z"/><path fill="#F4ABBA" d="M22.949 27.679c-.03 0-.06-.002-.091-.006-.401-.05-.686-.415-.637-.816l.426-3.448c.023-.188.117-.358.264-.478l4.448-3.624-7.108-3.833c-.356-.192-.488-.636-.296-.992.193-.356.636-.489.992-.297l8.072 4.353c.214.116.357.332.381.575.023.244-.077.482-.267.636l-5.07 4.13-.39 3.156c-.044.373-.3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9ad.svg b/browser/components/torpreferences/content/bridgemoji/1f9ad.svg new file mode 100644 index 000000000000..6904e81a5733 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9ad.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="#99AAB5"><path d="M3.016 5.201c-.062 0-.116-.043-.128-.105-.105-.555-.092-1.586.168-1.875.047-.053.129-.058.184-.01.053.048.058.13.01.184-.159.176-.216 1.072-.106 1.652.013.071-.033.139-.104.152-.008.002-.016.002-.024.002zm.517.9c-.057 0-.109-.038-.125-.096-.203-.74-.075-1.858-.008-2.097.019-.068.093-.107.16-.09.069.019.109.091.09.16-.069.246-.172 1.299.008 1.959.019.069-.022.141-.091.16-.011.003-.023.004-.034.004z"/><p [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9c1.svg b/browser/components/torpreferences/content/bridgemoji/1f9c1.svg new file mode 100644 index 000000000000..1f966c363b0f --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9c1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BBDDF5" d="M5 21.875s1.589 5.727 2 8.482c.464 3.111 3.571 5.571 11 5.571s10.536-2.461 11-5.571c.411-2.755 2-8.482 2-8.482H5z"/><path fill="#662113" d="M5.094 21.969c.25 1.219.694 1.994 1.344 1.594.65-.4 1.65-.193 1.344.625-.281.75.969 1.094 1.5.656.509-.419 1.555-.881 1.656.062.094.875 1.168 1.11 1.656.469.5-.656 1.875-.394 2.125.406s1.594.688 1.969.125c.355-.533.713-.885 1.312-.885V25c.6 0 .957.373 1.312.906.375.56 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9c3.svg b/browser/components/torpreferences/content/bridgemoji/1f9c3.svg new file mode 100644 index 000000000000..51a3f26d4d9d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9c3.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E3A919" d="M28.337 32.563l-2.579 3.082c-.552.553-1.758.007-1.758-.545V12.001c0-.552 2.372-4.501 5-4.501l-.028 23.007c0 1.134-.093 1.308-.635 2.056z"/><path fill="#FFD983" d="M26 10.454H9l.194-.348L12.74 7.5c.486-.379.831-.5 1.383-.5h14.363c.188 0 .514.277.344.47L26 10.454z"/><path fill="#FFCC4D" d="M25 36H10c-.552 0-1-.448-1-1V10.454S9.448 10 10 10h15c.552 0 1 .454 1 .454V35c0 .552-.448 1-1 1z"/><circle fill="#F4900 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9c5.svg b/browser/components/torpreferences/content/bridgemoji/1f9c5.svg new file mode 100644 index 000000000000..ab68cb1d6c19 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9c5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D39954" d="M26.617 32.441c-.02-.039-.373-.678-1.025-1.172.103-.421.056-.835-.307-1.103-.9-.662-8.82.226-9.386 3.057-.234 1.171.588 1.171 1.038.809-.137.499-.212 1.011-.169 1.49.024.269.261.467.531.444.029-.002.057-.007.084-.015.225-.06.381-.275.36-.516-.03-.334.022-.694.111-1.051.201.18.424.327.686.269.312-.07.51-.378.64-.712.015.212.046.348.05.363.069.259.333.411.593.345l.006-.002c.257-.069.411-.333.348-.591-.004-. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9c7.svg b/browser/components/torpreferences/content/bridgemoji/1f9c7.svg new file mode 100644 index 000000000000..cc92a9d18792 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9c7.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#939598" cx="18" cy="26" rx="18" ry="10"/><ellipse fill="#CCD6DD" cx="18" cy="24" rx="18" ry="10"/><path fill="#F4900C" d="M15.714 32.54L2.575 24.857c-1.262-.738-.929-3.558-.929-4.785l14.069-6.657c1.262-.738 3.308-.738 4.57 0L34.37 20.26c0 1.689.316 3.859-.947 4.597L20.285 32.54c-1.262.738-3.309.738-4.571 0z"/><path fill="#FFAC33" d="M15.714 29.279L2.575 21.596c-1.262-.738-1.262-1.934 0-2.672l13.139-7.683c1.262-.7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9c9.svg b/browser/components/torpreferences/content/bridgemoji/1f9c9.svg new file mode 100644 index 000000000000..16b0db823d27 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9c9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8A4B38" d="M26.875 14.6C25.1 12.778 21.55 12.778 18 12.778s-7.1 0-8.875 1.822c-2.663 2.733-5.325 9.72-3.55 15.186C7.35 35.252 15.132 35.814 18 35.814s10.65-.562 12.425-6.028c1.775-5.465-.887-12.453-3.55-15.186z"/><path fill="#A5503C" d="M25.58 14.412c-1.516-1.556-4.548-1.556-7.58-1.556s-6.064 0-7.58 1.556c-2.274 2.334-4.548 8.302-3.032 12.969C8.904 32.049 15.55 32.529 18 32.529s9.096-.48 10.611-5.148c1.516-4.668-.75 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9d9.svg b/browser/components/torpreferences/content/bridgemoji/1f9d9.svg new file mode 100644 index 000000000000..553708315577 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9d9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#C63900" cx="18" cy="19.024" rx="15" ry="3.529"/><path fill="#FA743E" d="M26.276 28.276h-16c-2.486 0-4.774 1.443-5.762 3.449-.368-.415-.907-.619-1.514-.619-1.1 0-2 .935-2 1.998V36h31v-1.931c0-3.199-2.411-5.793-5.724-5.793z"/><path fill="#FFDC5D" d="M14 25v3.234c0 .004.011.007.015.011.132.237 3.737 6.739 3.989 6.739.253 0 3.854-6.502 3.985-6.739.004-.004.01-.007.01-.011V25H14z"/><path fill="#F9CA55" d="M14 27.598c1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9da.svg b/browser/components/torpreferences/content/bridgemoji/1f9da.svg new file mode 100644 index 000000000000..5c9ea3625c0a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9da.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#ABDFFF" d="M20.769 20.273c-4.558 6.089-5.676 12.675-2.497 14.71 3.179 2.035 9.451-1.252 14.008-7.341 4.558-6.089 2.752-17.029 2.497-16.631-1.777 2.774-9.45 3.173-14.008 9.262z"/><path fill="#ABDFFF" d="M15.255 20.273c4.558 6.089 5.676 12.675 2.498 14.71-3.179 2.035-9.451-1.252-14.009-7.341S.904 10.673 1.247 11.011C5 14.71 10.698 14.184 15.255 20.273z"/><path fill="#55ACEE" d="M4.598 17.829c-.484-.808-1.158-1.652-.77 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9dc.svg b/browser/components/torpreferences/content/bridgemoji/1f9dc.svg new file mode 100644 index 000000000000..52f120c41f49 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9dc.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M7.399 17.278c-1.878 1.035-3.24 2.455-3.336 2.617-.239.404-.437 1.592-.276 2.415.145.741.177 2.238.66 2.915.198.277.107.759-.032 1.208-.419 1.353.306 3.199-.145 2.882-.918-.644-1.074-1.83-1.047-2.528.015-.393-.166-.884-.257-1.138-.059-.16-1.245-3.203-1.518-4.54-.148-.722-.333-1.302.275-2.155.903-1.266 2.094-2.996 3.311-3.885 2.012-1.471 3.936-1.687 3.936-1.687s-.911 3.532-1.571 3.896zm23-6.626c-.084.009-. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9e0.svg b/browser/components/torpreferences/content/bridgemoji/1f9e0.svg new file mode 100644 index 000000000000..653427da92a8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9e0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EA596E" d="M29.896 26.667c.003.283-.07.653-.146.958-.531 2.145-2.889 4.552-6.208 4.333-3.008-.198-5.458-1.642-5.458-3.667s2.444-3.667 5.458-3.667 6.335.018 6.354 2.043z"/><path fill="#DD2E44" d="M23.542 24.964c-1.619 0-5.314.448-6.162.448-1.498 0-2.713.94-2.713 2.1 0 .558.286 1.062.744 1.438 0 0 1.006 1.009 2.818.525.793-.212 2.083-1.786 4.354-2.036 1.131-.125 3.25.75 6.974.771.16-.344.193-.583.193-.583 0-2.027-3.19 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9e2.svg b/browser/components/torpreferences/content/bridgemoji/1f9e2.svg new file mode 100644 index 000000000000..c2dd6c6f2f1e --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9e2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse transform="rotate(-87.495 21.25 5.018)" fill="#2B7BB9" cx="21.249" cy="5.018" rx=".944" ry="1.565"/><path fill="#292F33" d="M29.831 27.74s3.523 1.385 5.185.088c.125-1.17-3.311-2.035-3.311-2.035l-1.874 1.947zM7.527 25.549S2.271 33.375.77 32.031c0 0-.425-1.397 1.23-4.218 1.656-2.822 5.527-2.264 5.527-2.264z"/><path fill="#1C6399" d="M19.766 4.82s-8.537.43-13.735 16.348c7.494 0 16.785.555 16.785.555s7.799 3.982 8.889 4.469 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9e6.svg b/browser/components/torpreferences/content/bridgemoji/1f9e6.svg new file mode 100644 index 000000000000..44fc31080aaa --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9e6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M27 19.276c-.983 0-1.893.294-2.667.784v4.549h-2.3c.175 2.603 2.319 4.667 4.966 4.667 2.761 0 5-2.239 5-5v-5H27z"/><path fill="#1C6399" d="M22.995 22.999l.005-16 9 .003-.005 16z"/><path fill="#1C6399" d="M24.202 21.451l6.354 6.354-6.878 6.878-6.354-6.354z"/><path fill="#55ACEE" d="M30 4h-5c-1.1 0-2 .9-2 2v2h9V6c0-1.1-.9-2-2-2zM15 15.276c-.983 0-1.893.294-2.667.784v4.549h-2.3c.175 2.603 2.319 4.667 4.966 4. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9e9.svg b/browser/components/torpreferences/content/bridgemoji/1f9e9.svg new file mode 100644 index 000000000000..ae4bf566814b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9e9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#78B159" d="M26.093 4.612c-.498.498-.629.839-.728 1.029-.66 1.266-1.455 1.676-2.78.351-.13-.13-4.087-4.267-4.741-5.017-.427-.49-1.081-.64-1.584-.262-.38.286-4.035 3.273-5.035 4.507-.774.955-.8 2.134-.079 2.856.326.326.727.449 1.151.578.552.169 1.763.068 2.47.775 1.133 1.133.54 2.924-.917 4.421-1.497 1.457-3.288 2.05-4.421.917-.708-.708-.606-1.918-.775-2.47-.129-.424-.252-.824-.578-1.151-.721-.721-1.9-.694-2.856.079-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9ea.svg b/browser/components/torpreferences/content/bridgemoji/1f9ea.svg new file mode 100644 index 000000000000..8116cfa1ca01 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9ea.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M14.563 14.414L25.47 3.505l6.961 6.962-10.908 10.908z"/><path fill="#68E090" d="M8.103 34.399C2.5 34 1.5 30.062 1.635 27.932c.322-5.07 15.601-16.551 15.601-16.551l12.517 1.93c.001 0-17.389 21.392-21.65 21.088z"/><path fill="#8899A6" d="M32.326 3.708C29.405.787 26.104-.649 24.954.502c-.013.013-.022.031-.034.044-.006.006-.015.008-.021.014L2.295 23.164c-1.412 1.412-2.19 3.29-2.19 5.288 0 1.997.778 3.875 2.19 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9ec.svg b/browser/components/torpreferences/content/bridgemoji/1f9ec.svg new file mode 100644 index 000000000000..689cc3e440fa --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9ec.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M3.019 26.246l3.432 3.432-.923.922-3.432-3.431z"/><path fill="#FFD983" d="M6.362 29.587l3.431 3.432-.923.923-3.43-3.432z"/><path fill="#FFAC33" d="M6.273 24.237l3.432 3.432-.923.923L5.35 25.16z"/><path fill="#EA596E" d="M8.998 26.962l3.432 3.432-.923.923-3.431-3.432zm3.909-9.359l3.431 3.432-.923.923-3.431-3.432z"/><path fill="#FFAC33" d="M15.631 20.329l3.432 3.431-.923.923-3.432-3.431z"/><path fill="#77B2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9ed.svg b/browser/components/torpreferences/content/bridgemoji/1f9ed.svg new file mode 100644 index 000000000000..fd3d583e58a9 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9ed.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#F4900C" cx="18" cy="18" r="18"/><circle fill="#FFD983" cx="18" cy="18" r="14.5"/><circle fill="#F5F8FA" cx="18" cy="18" r="13"/><path fill="#CCD6DD" d="M18 8l1.531 6.304 5.54-3.375-3.375 5.54L28 18l-6.304 1.531 3.375 5.54-5.54-3.375L18 28l-1.531-6.304-5.54 3.375 3.375-5.54L8 18l6.304-1.531-3.375-5.54 5.54 3.375z"/><path fill="#292F33" d="M17.343 20.748l8.777 5.381-5.379-8.778z"/><path fill="#DD2E44" d="M18.657 15. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9ee.svg b/browser/components/torpreferences/content/bridgemoji/1f9ee.svg new file mode 100644 index 000000000000..c619bb0eb5e7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9ee.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M3 16v-2h30v2zm0 7v-2h30v2zm0 7v-2h30v2zM3 9V7h30v2z"/><path fill="#D5AB88" d="M35 33V1c0-.552-.448-1-1-1h-1c-.552 0-1 .448-1 1H4c0-.552-.448-1-1-1H2c-.552 0-1 .448-1 1v32h34zM4 4h28v29H4V4z"/><path fill="#3B94D9" d="M19 5.5c-.829 0-1.5.671-1.5 1.5 0-.829-.671-1.5-1.5-1.5s-1.5.671-1.5 1.5c0-.829-.671-1.5-1.5-1.5s-1.5.671-1.5 1.5c0-.829-.671-1.5-1.5-1.5S8.5 6.171 8.5 7c0-.829-.671-1.5-1.5-1.5S5.5 6.171 5.5 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9f2.svg b/browser/components/torpreferences/content/bridgemoji/1f9f2.svg new file mode 100644 index 000000000000..e81b4644e293 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9f2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M19.469 8.319l-3.854-4.671c-.355-.43-.991-.491-1.421-.136l-3.376 2.785 5.138 6.228 3.376-2.785c.431-.355.492-.991.137-1.421z"/><path fill="#BE1931" d="M29.543 25.021l-6.228-5.138s-2.395 3.132-5.006 6.152c-1.986 2.298-6.093 2.215-8.406-.098s-2.395-6.42-.098-8.406c3.02-2.611 6.152-5.006 6.152-5.006l-5.138-6.228s-5.122 3.928-6.613 5.43c-5.468 5.508-5.487 14.418.001 19.906s14.398 5.468 19.906.001c1.502-1.491 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9f5.svg b/browser/components/torpreferences/content/bridgemoji/1f9f5.svg new file mode 100644 index 000000000000..3210fbeb543a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9f5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#553788" d="M35 36c-.419 0-.809-.265-.948-.684C33.388 33.326 31.595 32 25 32c-.553 0-1-.447-1-1s.447-1 1-1c5.635 0 9.653.797 10.948 4.684.175.524-.108 1.091-.632 1.265-.105.034-.212.051-.316.051z"/><path fill="#553788" d="M3 3h22v30H3z"/><path fill="#C1694F" d="M26 4H2c-.55 0-2-3-2-3 0-.55.45-1 1-1h26c.55 0 1 .45 1 1 0 0-1.45 3-2 3zm0 28H2c-.55 0-2 3-2 3 0 .55.45 1 1 1h26c.55 0 1-.45 1-1 0 0-1.45-3-2-3z"/><path fill= [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1f9f9.svg b/browser/components/torpreferences/content/bridgemoji/1f9f9.svg new file mode 100644 index 000000000000..2bcbda87e3e8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1f9f9.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M19.175 16.825c-.706-.706-.706-1.861 0-2.567L32.433 1C33.139.294 34.294.294 35 1c.706.706.706 1.861 0 2.567L21.742 16.825c-.706.706-1.861.706-2.567 0z"/><path fill="#66757F" d="M8.839 27.365l-.204-.204c-.941-.941-.941-2.48 0-3.42l8.35-8.35c.941-.941 2.48-.941 3.42 0l.204.204c.941.941.941 2.48 0 3.42l-8.35 8.35c-.94.941-2.48.941-3.42 0z"/><path fill="#FFCC4D" d="M19.389 26.341c1.688-1.688.671-5.441-1.809-7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa73.svg b/browser/components/torpreferences/content/bridgemoji/1fa73.svg new file mode 100644 index 000000000000..03e70ca89e71 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa73.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#78B159" d="M31 6V1.955c0-.527-.428-.955-.955-.955H5.955C5.428 1 5 1.428 5 1.955V6L0 31l14 4 4-10.545L22 35l14-4-5-25z"/><g fill="#5D9040"><path d="M5 4h26v2H5zm8.782 2h-2.074c-1.378 4.107-5.46 5.399-7.87 5.807l-.421 2.107c3.606-.408 8.9-2.403 10.365-7.914z"/><path d="M32.583 13.914l-.421-2.107c-2.41-.408-6.491-1.701-7.87-5.807h-2.074c1.464 5.511 6.759 7.506 10.365 7.914zM17 6v21.091l1-2.636 1 2.636V6z"/><path d="M18 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa80.svg b/browser/components/torpreferences/content/bridgemoji/1fa80.svg new file mode 100644 index 000000000000..fc9af9be9d2d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa80.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#BE1931" cx="17.722" cy="21.507" rx="12.917" ry="13.993"/><ellipse fill="#A0041E" cx="15.569" cy="21.507" rx="12.917" ry="13.993"/><path fill="#99AAB5" d="M28.298 13.741c-1.643 0-3.457-.825-5.227-1.629-2.124-.966-4.322-1.966-5.46-1.113l-1.167-1.555c2.06-1.544 4.79-.303 7.431.898 2.023.92 4.117 1.868 5.327 1.263.664-.331 1.155-1.195 1.459-2.566l1.899.422c-.444 2-1.259 3.27-2.49 3.885-.558.278-1.153.395-1.772.395z"/ [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa81.svg b/browser/components/torpreferences/content/bridgemoji/1fa81.svg new file mode 100644 index 000000000000..fd8605e46847 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa81.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M22.45 32.289L.592 18.752 6.55.711l18.042 5.958z"/><path fill="#269" d="M20.543 29.5c-.366 0-.719-.201-.895-.551L6.929 3.687c-.249-.493-.05-1.095.443-1.343.494-.249 1.095-.05 1.343.443l12.72 25.264c.248.493.05 1.094-.444 1.343-.143.072-.297.106-.448.106z"/><path fill="#269" d="M3.12 18.48c-.366 0-.718-.201-.894-.55-.249-.493-.05-1.095.443-1.343l18.947-9.54c.49-.25 1.094-.05 1.343.443.248.493.05 1.095-.443 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa83.svg b/browser/components/torpreferences/content/bridgemoji/1fa83.svg new file mode 100644 index 000000000000..3de58a8f2cc5 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa83.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M31.497 6.06s.244-4.293-6.702-3.83c-8.617.574-18.599 9.155-18.95 17.159-.267 6.089 8.109 1.203 11.139-1.96-.045-.045 3.282 1.426 4.752 4.545-3.267 2.733-8.047 4.803-11.644 5.109-6.282.535-8.546-2.727-7.557-9.15C3.758 10 13.588.858 24.604.795c8.377-.048 6.893 5.265 6.893 5.265z"/><path fill="#662113" d="M32.19 32.317c.351-1.768-.27-19.086-4.411-22.615S5.541 7.73 3.881 8.435c-2.24.95-1.352 3.835.184 3.93 5. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa90.svg b/browser/components/torpreferences/content/bridgemoji/1fa90.svg new file mode 100644 index 000000000000..46a0c53cea53 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa90.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#FFCC4D" cx="18" cy="18" r="10.694"/><path fill="#F4900C" d="M10.229 22.751c-.985.067-1.689-.308-2.203-.917.214.557.487 1.081.788 1.588.771.289 1.591.41 2.54-.272-.463-.227-.88-.415-1.125-.399zM23.086 8.608c.045.328-.187.5-.75.363-.955-.232-1.793.776-2.274 1.619-.947 1.657-4.854 3.524-4.857 2.087-.001-.679-3.452.843-3.893.161-.417-.644-1.663-.396-1.921-1.168-1.135 1.544-1.869 3.402-2.04 5.422.377.718.864 1.282 1.35 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa91.svg b/browser/components/torpreferences/content/bridgemoji/1fa91.svg new file mode 100644 index 000000000000..8db580193f88 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa91.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A4523A" d="M12 20c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2s2-.9 2-2V22c0-1.1-.9-2-2-2zm18 0c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2s2-.9 2-2V22c0-1.1-.9-2-2-2z"/><path fill="#C1694F" d="M13 2h16v8H13z"/><path fill="#C1694F" d="M16 9h2v13h-2zm4 0h2v13h-2zm4 0h2v13h-2zM7 23c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2s2-.9 2-2v-9c0-1.1-.9-2-2-2z"/><path fill="#C1694F" d="M25 21c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2s2-.9 2-2V23c0-1.1-.9-2-2-2zM12 0c-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa95.svg b/browser/components/torpreferences/content/bridgemoji/1fa95.svg new file mode 100644 index 000000000000..da6e25d06b84 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa95.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#67757F" cx="10.047" cy="16.953" r="1"/><circle fill="#67757F" cx="1.047" cy="25.953" r="1"/><circle fill="#67757F" cx="19.047" cy="25.953" r="1"/><circle fill="#67757F" cx="10.047" cy="34.953" r="1"/><circle fill="#67757F" cx="3.547" cy="19.828" r="1"/><circle fill="#67757F" cx="16.214" cy="32.495" r="1"/><path fill="#292F33" d="M32.339 5.338l-15.45 17.334-3.561-3.56L30.66 3.66z"/><ellipse transform="rotate(-52.01 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fa97.svg b/browser/components/torpreferences/content/bridgemoji/1fa97.svg new file mode 100644 index 000000000000..c9c21ca2a296 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fa97.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFE8B6" d="M17.483 30.971c-.574.051-1.099-.36-1.168-.93L13.427 5.136c-.069-.57.676-1.133 1.657-1.22.981-.087 1.811.336 1.844.909l1.541 25.024c.031.574-.412 1.07-.986 1.122zm4.199-.146c-.574-.011-1.051-.476-1.058-1.051l-.192-25.071c-.007-.574.791-1.054 1.773-1.034.982.019 1.76.528 1.731 1.102l-1.157 25.045c-.029.574-.522 1.02-1.097 1.009zm4.185.306c-.57-.073-.993-.586-.939-1.158l2.501-24.947c.054-.572.899-.963 1.873- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fab6.svg b/browser/components/torpreferences/content/bridgemoji/1fab6.svg new file mode 100644 index 000000000000..8e70d6cd5c39 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fab6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M4.048 29.644c-.811-.558-1.541-4.073-.936-4.404.738-.402.686.835 2.255 2.362 1.569 1.528 6.47.913 7.708 1.326 1.363.455-6.385 2.533-9.027.716z"/><path fill="#D99E82" d="M5.367 27.603C4 22 4.655 18.919 5.433 16.861 6.8 13.24 16.699 5.169 23.8 2.637 25.678 1.967 31.62 1 35 1c.589 2.332-1.174 6.717-1.62 7.518-1.009 1.81-3.564 4.273-8.646 9.482-.252.258-5.119-.46-5.376-.191-.283.296 4.044 1.579 3.755 1.889-.7 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fad0.svg b/browser/components/torpreferences/content/bridgemoji/1fad0.svg new file mode 100644 index 000000000000..34e68d6b49b1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fad0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M8.089 1s5.858.616 8.54 4.06c3.206 4.117.891 7.962.891 7.962s-4.825-.425-7.03-3.157C7.287 5.896 8.089 1 8.089 1z"/><path fill="#77B255" d="M21.901 1s.913 4.617-1.006 7.47c-2.293 3.41-5.676 2.54-5.676 2.54s-.813-3.784.691-6.106C18.096 1.53 21.901 1 21.901 1z"/><ellipse fill="#5864B7" cx="23.737" cy="11.536" rx="6.916" ry="6.027"/><path fill="#334372" d="M19.34 16.996c0-.851-.124-1.64-.34-2.373 1.231-.409 2 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fad2.svg b/browser/components/torpreferences/content/bridgemoji/1fad2.svg new file mode 100644 index 000000000000..b84ce6a1f480 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fad2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#909B50" d="M13.241 23.993c1.274-1.617 4.455-.891 6.416.535s3.089 4.337 1.901 5.941-4.158 1.96-6.416.416c-2.258-1.545-3.446-4.931-1.901-6.892zm4.455-20.792c2.802.342 3.208 2.97 2.792 5.703-.416 2.733-2.436 5.347-4.693 5.228-2.257-.119-3.149-3.446-2.792-6.238s2.258-4.99 4.693-4.693z"/><path fill="#C1694F" d="M1.3 32.876c-.08 0-.161-.016-.239-.048-.319-.133-.47-.498-.337-.817 2.393-5.756 6.062-8.645 8.72-10.055 1.57-.8 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/1fad6.svg b/browser/components/torpreferences/content/bridgemoji/1fad6.svg new file mode 100644 index 000000000000..9e6894dafeee --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/1fad6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#269" cx="17.5" cy="31.5" rx="8" ry="2"/><path fill="#3B88C3" d="M11.173 31.777s3.297.757 6.371.668c2.539-.074 5.614-.356 6.505-.757 0 0-1.069 1.381-6.416 1.381s-6.46-1.292-6.46-1.292z"/><ellipse fill="#269" cx="17.5" cy="15.25" rx="8.5" ry="2.25"/><path fill="#3B88C3" d="M33.582 16.654c3.518-.202 2.185 1.133 1.072 1.712-.505.262-1.515 1.738-2.098 4.234-.455 1.948-.847 5.658-5.213 5.391-2.994-.183-.045-7.084-.045- [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/23f0.svg b/browser/components/torpreferences/content/bridgemoji/23f0.svg new file mode 100644 index 000000000000..ea9ad1431661 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/23f0.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M20 6.042c0 1.112-.903 2.014-2 2.014s-2-.902-2-2.014V2.014C16 .901 16.903 0 18 0s2 .901 2 2.014v4.028z"/><path fill="#FFAC33" d="M9.18 36c-.224 0-.452-.052-.666-.159-.736-.374-1.035-1.28-.667-2.027l8.94-18.127c.252-.512.768-.835 1.333-.835s1.081.323 1.333.835l8.941 18.127c.368.747.07 1.653-.666 2.027-.736.372-1.631.07-1.999-.676L18.121 19.74l-7.607 15.425c-.262.529-.788.835-1.334.835z"/><path fill="#58595 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2600.svg b/browser/components/torpreferences/content/bridgemoji/2600.svg new file mode 100644 index 000000000000..8602baef7e5a --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2600.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M16 2s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2V2zm18 14s2 0 2 2-2 2-2 2h-2s-2 0-2-2 2-2 2-2h2zM4 16s2 0 2 2-2 2-2 2H2s-2 0-2-2 2-2 2-2h2zm5.121-8.707s1.414 1.414 0 2.828-2.828 0-2.828 0L4.878 8.708s-1.414-1.414 0-2.829c1.415-1.414 2.829 0 2.829 0l1.414 1.414zm21 21s1.414 1.414 0 2.828-2.828 0-2.828 0l-1.414-1.414s-1.414-1.414 0-2.828 2.828 0 2.828 0l1.414 1.414zm-.413-18.172s-1.414 1.414-2.828 0 0-2.828 0-2.828l [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2602.svg b/browser/components/torpreferences/content/bridgemoji/2602.svg new file mode 100644 index 000000000000..7c633302e7e8 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2602.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M9 28.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5v.5s0 3 3 3 3-3 3-3V3.5c0-.829.671-1.5 1.5-1.5s1.5.671 1.5 1.5V29s0 6-6 6-6-6-6-6v-.5z"/><path fill="#744EAA" d="M19.5 4C28.612 4 36 9.82 36 17c0 0 0 2-1 2s-3-2-3-2H7s-2 2-3 2-1-2-1-2C3 9.82 10.387 4 19.5 4z"/><path fill="#9266CC" d="M19.5 4C26.403 4 32 9.82 32 17c0 0 0 2-2 2s-5-2-5-2H14s-3 2-5 2-2-2-2-2C7 9.82 12.596 4 19.5 4z"/><path fill="#744EAA" d="M19.5 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2604.svg b/browser/components/torpreferences/content/bridgemoji/2604.svg new file mode 100644 index 000000000000..07df915c4f9b --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2604.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5DADEC" d="M33.662.049c-1.148-.077-5.869-.75-17.522 6.665C4.488 14.129-1.646 26.609 3.604 33.329c1.104 1.413 5.518 4.283 10.682.53 3.247-2.359 4.242-7.52 7.15-14.434 1.986-4.723 6.444-13.594 12.668-17.611 1.942-.97.22-1.72-.442-1.765z"/><path fill="#8CCAF7" d="M16.625 13.651c-.265-1.059.971-4.281 1.324-5.164-6.437 2.929-12.509 11.616-12.47 13.18.006.227.139.305.42.193 5.398-2.166 8.882 1.07 9.807 3.751.095.274.214.4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/260e.svg b/browser/components/torpreferences/content/bridgemoji/260e.svg new file mode 100644 index 000000000000..e65124faa548 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/260e.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BE1931" d="M36 11.5C36 8.462 33 4 18 4S0 8.462 0 11.5c0 .045.019.076.022.119-.012.196-.022.402-.022.631C0 14.873 2.239 16 5 16s5-1.127 5-3.75c0-.218-.021-.412-.051-.597C12.374 11.302 15.102 11 18 11s5.626.302 8.051.653c-.03.185-.051.379-.051.597 0 2.623 2.238 3.75 5 3.75s5-1.127 5-3.75c0-.225-.009-.429-.024-.621.004-.046.024-.08.024-.129z"/><path fill="#DD2E44" d="M34.934 23c-.482-1.031-2.31-4.19-3.968-7.007C29.408 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2693.svg b/browser/components/torpreferences/content/bridgemoji/2693.svg new file mode 100644 index 000000000000..09f3fe4162e1 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2693.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#269" d="M30.5 18.572L26 25h2.575c-1.13 3.988-4.445 7.05-8.575 7.81V17h3c1.104 0 2-.896 2-2s-.896-2-2-2h-3v-1.349h-4V13h-3c-1.104 0-2 .896-2 2s.896 2 2 2h3v15.81c-4.13-.76-7.445-3.821-8.575-7.81H10l-4.5-6.428L1 25h3.33C5.705 31.289 11.299 36 18 36s12.295-4.711 13.67-11H35l-4.5-6.428z"/><path fill="#269" d="M18 0c-3.314 0-6 2.686-6 6s2.686 6 6 6 6-2.686 6-6-2.686-6-6-6zm0 9c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2696.svg b/browser/components/torpreferences/content/bridgemoji/2696.svg new file mode 100644 index 000000000000..61af8441c5a7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2696.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M3.923 22.923c-.212.511-.798.751-1.308.539-.51-.213-.751-.798-.539-1.308L6.693 9.616c.212-.51.798-.751 1.307-.539.51.213.751.798.539 1.308L3.923 22.923z"/><path fill="#66757F" d="M13.923 22.154c.212.51-.029 1.095-.539 1.308-.51.212-1.095-.028-1.308-.539L7.461 10.385c-.212-.51.029-1.095.539-1.308.51-.212 1.095.029 1.308.539l4.615 12.538zm10.001.769c-.213.511-.799.751-1.309.539-.51-.213-.75-.798-.538-1.308l [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/26bd.svg b/browser/components/torpreferences/content/bridgemoji/26bd.svg new file mode 100644 index 000000000000..f24749cb5eab --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/26bd.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#F5F8FA" cx="18" cy="18" r="18"/><path d="M18 11c-.552 0-1-.448-1-1V3c0-.552.448-1 1-1s1 .448 1 1v7c0 .552-.448 1-1 1zm-6.583 4.5c-.1 0-.202-.015-.302-.047l-8.041-2.542c-.527-.167-.819-.728-.652-1.255.166-.527.73-.818 1.255-.652l8.042 2.542c.527.167.819.729.652 1.255-.136.426-.53.699-.954.699zm13.625-.291c-.434 0-.833-.285-.96-.722-.154-.531.151-1.085.682-1.239l6.75-1.958c.531-.153 1.085.153 1.238.682.154.531-.151 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/26f2.svg b/browser/components/torpreferences/content/bridgemoji/26f2.svg new file mode 100644 index 000000000000..659c22850c5d --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/26f2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M1 22.923v6.538C1 33.073 8.611 36 18 36s17-2.927 17-6.538v-6.538H1z"/><ellipse fill="#E1E8ED" cx="18" cy="22.923" rx="17" ry="6.538"/><path fill="#AAB8C2" d="M18 20.308c6.7 0 12.314 1.668 13.913 3.923.297-.419.472-.855.472-1.308 0-2.889-6.44-5.231-14.385-5.231S3.615 20.034 3.615 22.923c0 .452.175.889.472 1.308C5.686 21.976 11.3 20.308 18 20.308z"/><ellipse fill="#3B88C3" cx="18" cy="24.231" rx="13.913" ry [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/26f5.svg b/browser/components/torpreferences/content/bridgemoji/26f5.svg new file mode 100644 index 000000000000..c76c0dedfb1f --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/26f5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A7A9AC" d="M20 26c0 .553-.447 1-1 1-.552 0-1-.447-1-1V1c0-.552.448-1 1-1 .553 0 1 .448 1 1v25z"/><path fill="#D1D3D4" d="M3 24h31v8H12c-6 0-9-8-9-8z"/><path fill="#55ACEE" d="M0 30h36v6H0z"/><path fill="#FFAC33" d="M5 22s2-5 5-9 8-8 8-8-1 11-1 16v1s-3-1-6-1-6 1-6 1z"/><path fill="#F4900C" d="M20 2s6 6 9 11c2.771 4.618 4 9 4 9s-3-1-6-1-6 1-6 1v-1c0-9-1-19-1-19z"/><path fill="#E6E7E8" d="M2 24c-.552 0-1 .447-1 1s.448 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2708.svg b/browser/components/torpreferences/content/bridgemoji/2708.svg new file mode 100644 index 000000000000..ebce3afbc783 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2708.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M30 23.828c-.391.392-1.023.392-1.414 0l-1.414-1.414c-.392-.391-.392-1.024 0-1.414L30 18.172c.391-.391 1.023-.391 1.414 0l1.414 1.414c.392.391.392 1.024 0 1.414L30 23.828zm-15-15c-.391.392-1.023.392-1.414 0l-1.414-1.414c-.392-.391-.392-1.023 0-1.414L15 3.172c.391-.391 1.023-.391 1.414 0l1.414 1.414c.392.391.392 1.023 0 1.414L15 8.828z"/><path fill="#55ACEE" d="M2 22c2 0 11 1 11 1s1 9 1 11-2 2-3 1-4-6-4-6-5 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/270f.svg b/browser/components/torpreferences/content/bridgemoji/270f.svg new file mode 100644 index 000000000000..a9b69e1b2952 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/270f.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z"/><path fill="#EA596E" d="M13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2728.svg b/browser/components/torpreferences/content/bridgemoji/2728.svg new file mode 100644 index 000000000000..347ad12abbd7 --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2728.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M34.347 16.893l-8.899-3.294-3.323-10.891c-.128-.42-.517-.708-.956-.708-.439 0-.828.288-.956.708l-3.322 10.891-8.9 3.294c-.393.146-.653.519-.653.938 0 .418.26.793.653.938l8.895 3.293 3.324 11.223c.126.424.516.715.959.715.442 0 .833-.291.959-.716l3.324-11.223 8.896-3.293c.391-.144.652-.518.652-.937 0-.418-.261-.792-.653-.938z"/><path fill="#FFCC4D" d="M14.347 27.894l-2.314-.856-.9-3.3c-.118-.436-.513-.738-. [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/bridgemoji/2744.svg b/browser/components/torpreferences/content/bridgemoji/2744.svg new file mode 100644 index 000000000000..258c161bb9bb --- /dev/null +++ b/browser/components/torpreferences/content/bridgemoji/2744.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#88C9F9" d="M19 27.586V8.415l4.828-4.829s.707-.707 0-1.415c-.707-.707-1.414 0-1.414 0L19 5.586V1s0-1-1-1-1 1-1 1v4.586l-3.414-3.415s-.707-.707-1.414 0c-.707.708 0 1.415 0 1.415L17 8.415v19.171l-4.828 4.828s-.707.707 0 1.414 1.414 0 1.414 0L17 30.414V35s0 1 1 1 1-1 1-1v-4.586l3.414 3.414s.707.707 1.414 0 0-1.414 0-1.414L19 27.586z"/><path fill="#88C9F9" d="M34.622 20.866c-.259-.966-1.225-.707-1.225-.707l-6.595 1.767-1 [...] \ No newline at end of file diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.jsm b/browser/components/torpreferences/content/builtinBridgeDialog.jsm new file mode 100644 index 000000000000..6eed194eedf6 --- /dev/null +++ b/browser/components/torpreferences/content/builtinBridgeDialog.jsm @@ -0,0 +1,113 @@ +"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(onSubmit) { + this.onSubmit = onSubmit; + this._dialog = null; + } + + 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); + + 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.setAttribute("label", types[type].label); + types[type].elemRadio.setAttribute("hidden", "false"); + types[type].elemDescr.textContent = types[type].descr; + types[type].elemDescr.removeAttribute("hidden"); + }); + + if ( + TorSettings.bridges.enabled && + TorSettings.bridges.source == TorBridgeSource.BuiltIn + ) { + radioGroup.selectedItem = + types[TorSettings.bridges.builtin_type]?.elemRadio; + } else { + radioGroup.selectedItem = null; + } + + this._dialog.addEventListener("dialogaccept", e => { + this.onSubmit(radioGroup.value); + }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + TorStrings.settings.learnMoreCircumventionURL, + "tab" + ); + }); + + // Hack: see the CSS + this._dialog.style.minWidth = "0"; + this._dialog.style.minHeight = "0"; + } + + 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/builtinBridgeDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.xhtml b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml new file mode 100644 index 000000000000..61c6110f327a --- /dev/null +++ b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml @@ -0,0 +1,31 @@ +<?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"> + <radio id="torPreferences-builtinBridges-radioObfs" value="obfs4" hidden="true"/> + <html:div id="torPreferences-builtinBridges-descrObfs" class="indent" hidden="true">​</html:div> + <radio id="torPreferences-builtinBridges-radioSnowflake" value="snowflake" hidden="true"/> + <html:div id="torPreferences-builtinBridges-descrSnowflake" class="indent" hidden="true">​</html:div> + <radio id="torPreferences-builtinBridges-radioMeekAzure" value="meek-azure" hidden="true"/> + <html:div id="torPreferences-builtinBridges-descrMeekAzure" class="indent" hidden="true">​</html:div> + </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/check.svg b/browser/components/torpreferences/content/check.svg new file mode 100644 index 000000000000..34b90800c71b --- /dev/null +++ b/browser/components/torpreferences/content/check.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M6.02301 13L1.18301 8.16099C1.07063 8.04262 1.00892 7.88505 1.01104 7.72185C1.01316 7.55865 1.07893 7.40273 1.19434 7.28732C1.30975 7.17191 1.46567 7.10613 1.62887 7.10402C1.79207 7.1019 1.94964 7.16361 2.06801 7.27599L6.37501 11.584L13.934 4.02299C14.0517 3.90617 14.2107 3.84061 14.3765 3.84061C14.5423 3.84061 14.7014 3.90617 14.819 4.02299C14.9358 4.14063 15.0014 4.29969 15.0014 4.46549C15.0014 4.63128 14.9358 4.79034 14.819 4.90799L6.72901 12.998L6.02301 13Z" fill="context-fill"/> +</svg> diff --git a/browser/components/torpreferences/content/connectionCategory.inc.xhtml b/browser/components/torpreferences/content/connectionCategory.inc.xhtml new file mode 100644 index 000000000000..15cf24cfe695 --- /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 000000000000..ff2593b57fb4 --- /dev/null +++ b/browser/components/torpreferences/content/connectionPane.js @@ -0,0 +1,1158 @@ +"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", + currentDescription: "#torPreferences-currentBridges-description", + currentDescriptionText: "#torPreferences-currentBridges-descriptionText", + 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", + 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", + label: "#torPreferences-advanced-label", + button: "#torPreferences-advanced-button", + torLogsLabel: "label#torPreferences-torLogs", + torLogsButton: "button#torPreferences-buttonTorLogs", + }, + }; /* selectors */ + + const retval = { + // cached frequently accessed DOM elements + _enableQuickstartCheckbox: null, + + _internetStatus: InternetStatus.Unknown, + + _controller: null, + + _currentBridge: "", + + // 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); + + const 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; + { + const learnMore = prefpane.querySelector( + selectors.torPreferences.learnMore + ); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute( + "href", + TorStrings.settings.learnMoreTorBrowserURL + ); + if (TorStrings.settings.learnMoreTorBrowserURL.startsWith("about:")) { + learnMore.setAttribute("useoriginprincipal", "true"); + } + } + + // 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", () => { + 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.hasBootstrapEverFailed) { + 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; + { + const learnMore = prefpane.querySelector(selectors.bridges.learnMore); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL); + if (TorStrings.settings.learnMoreBridgesURL.startsWith("about:")) { + learnMore.setAttribute("useoriginprincipal", "true"); + } + } + + // 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 = () => { + const currentValue = location.value; + locationEntries.textContent = ""; + const createItem = (value, label, disabled) => { + const item = document.createXULElement("menuitem"); + item.setAttribute("value", value); + item.setAttribute("label", label); + if (disabled) { + item.setAttribute("disabled", "true"); + } + return item; + }; + 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) + ); + 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 ( + !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 bridgeDescription = prefpane.querySelector( + selectors.bridges.currentDescription + ); + bridgeDescription.querySelector( + selectors.bridges.currentDescriptionText + ).textContent = TorStrings.settings.bridgeCurrentDescription; + const bridgeTemplate = prefpane.querySelector( + selectors.bridges.cardTemplate + ); + { + const learnMore = bridgeTemplate.querySelector( + selectors.bridges.cardLearnMore + ); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute( + "href", + TorStrings.settings.learnMoreBridgesCardURL + ); + if (TorStrings.settings.learnMoreBridgesCardURL.startsWith("about:")) { + learnMore.setAttribute("useoriginprincipal", "true"); + } + } + 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); + + let emojiAnnotations; + this._addBridgeCard = bridgeString => { + const card = bridgeTemplate.cloneNode(true); + 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) { + // 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 img = document.createElement("img"); + const cp = e.codePointAt(0).toString(16); + img.setAttribute( + "src", + `chrome://browser/content/torpreferences/bridgemoji/${cp}.svg` + ); + img.setAttribute("alt", e); + img.setAttribute("title", emojiAnnotations[e]); + img.className = "emoji"; + return img; + }); + 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.substring(0, style.width.length - 2); + const height = style.height.substring(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") || + card.classList.contains("currently-connected") + ) { + 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._confirmBridgeRemoval(); + }); + this._populateBridgeCards = () => { + const collapseThreshold = 4; + + const newStrings = new Set(TorSettings.bridges.bridge_strings); + const numBridges = newStrings.size; + if (!newStrings.size) { + bridgeHeader.setAttribute("hidden", "true"); + bridgeDescription.setAttribute("hidden", "true"); + bridgeCards.setAttribute("hidden", "true"); + showAll.setAttribute("hidden", "true"); + removeAll.setAttribute("hidden", "true"); + bridgeCards.textContent = ""; + return; + } + bridgeHeader.removeAttribute("hidden"); + bridgeDescription.removeAttribute("hidden"); + 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 + ? 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"); + } + }; + // Use a promise to avoid blocking the population of the page + // FIXME: Stop using a JSON file, and switch to properties + fetch( + "chrome://browser/content/torpreferences/bridgemoji-annotations.json" + ).then(async res => { + const annotations = await res.json(); + const bcp47 = Services.locale.appLocaleAsBCP47; + const dash = bcp47.indexOf("-"); + const lang = dash !== -1 ? bcp47.substring(dash) : bcp47; + if (bcp47 in annotations) { + emojiAnnotations = annotations[bcp47]; + } else if (lang in annotations) { + emojiAnnotations = annotations[lang]; + } else { + // At the moment, nb does not have annotations! + emojiAnnotations = annotations.en; + } + 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); + this._checkBridgeCardsHeight(); + }; + 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].substring(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); + { + const 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); + { + const 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(); + }); + } + + { + 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 = + TorStrings.settings.advancedHeading; + prefpane.querySelector(selectors.advanced.label).textContent = + TorStrings.settings.advancedLabel; + { + const 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); + const torLogsButton = prefpane.querySelector( + selectors.advanced.torLogsButton + ); + torLogsButton.setAttribute("label", TorStrings.settings.showLogs); + torLogsButton.addEventListener("command", () => { + this.onViewTorLogs(); + }); + + Services.obs.addObserver(this, TorConnectTopics.StateChange); + }, + + init() { + this._populateXUL(); + + const onUnload = () => { + window.removeEventListener("unload", onUnload); + gConnectionPane.uninit(); + }; + window.addEventListener("unload", onUnload); + + window.addEventListener("resize", () => { + this._checkBridgeCardsHeight(); + }); + window.addEventListener("hashchange", () => { + 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: { + const 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) { + const clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( + Ci.nsIClipboardHelper + ); + clipboard.copyString(addressElem.value); + }, + + 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(); + }); + }, + + onAddBuiltinBridge() { + const builtinBridgeDialog = new BuiltinBridgeDialog(aBridgeType => { + 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(); + }); + }); + builtinBridgeDialog.openDialog(gSubDialog); + }, + + // called when the request bridge button is activated + onRequestBridge() { + const requestBridgeDialog = new RequestBridgeDialog(aBridges => { + if (aBridges.length) { + const 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(); + }); + } + }); + requestBridgeDialog.openDialog(gSubDialog); + }, + + onAddBridgeManually() { + const provideBridgeDialog = new ProvideBridgeDialog(aBridgeString => { + if (aBridgeString.length) { + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.UserProvided; + TorSettings.bridges.bridge_strings = aBridgeString; + } else { + TorSettings.bridges.enabled = false; + TorSettings.bridges.source = TorBridgeSource.Invalid; + TorSettings.bridges.bridge_strings = ""; + } + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + provideBridgeDialog.openDialog(gSubDialog); + }, + + onAdvancedSettings() { + const connectionSettingsDialog = new ConnectionSettingsDialog(); + connectionSettingsDialog.openDialog(gSubDialog); + }, + + onViewTorLogs() { + const 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. + /* eslint-disable */ + const emojis = [ + "👽️", "🤖", "🧠", "👁️", "🧙", "🧚", "🧜", "🐵", "🦧", "🐶", "🐺", "🦊", "🦝", "🐱", "🦁", "🐯", + "🐴", "🦄", "🦓", "🦌", "🐮", "🐷", "🐗", "🐪", "🦙", "🦒", "🐘", "🦣", "🦏", "🐭", "🐰", "🐿️", + "🦔", "🦇", "🐻", "🐨", "🦥", "🦦", "🦘", "🐥", "🐦️", "🕊️", "🦆", "🦉", "🦤", "🪶", "🦩", "🦚", + "🦜", "🐊", "🐢", "🦎", "🐍", "🐲", "🦕", "🐳", "🐬", "🦭", "🐟️", "🐠", "🦈", "🐙", "🐚", "🐌", + "🦋", "🐛", "🐝", "🐞", "💐", "🌹", "🌺", "🌻", "🌷", "🌲", "🌳", "🌴", "🌵", "🌿", "🍁", "🍇", + "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🥭", "🍏", "🍐", "🍑", "🍒", "🍓", "🫐", "🥝", "🍅", "🫒", + "🥥", "🥑", "🍆", "🥕", "🌽", "🌶️", "🥬", "🥦", "🧅", "🍄", "🥜", "🥐", "🥖", "🥨", "🥯", "🥞", + "🧇", "🍔", "🍕", "🌭", "🌮", "🍿", "🦀", "🦞", "🍨", "🍩", "🍪", "🎂", "🧁", "🍫", "🍬", "🍭", + "🫖", "🧃", "🧉", "🧭", "🏔️", "🌋", "🏕️", "🏝️", "🏡", "⛲️", "🎠", "🎡", "🎢", "💈", "🚆", "🚋", + "🚍️", "🚕", "🚗", "🚚", "🚜", "🛵", "🛺", "🛴", "🛹", "🛼", "⚓️", "⛵️", "🛶", "🚤", "🚢", "✈️", + "🚁", "🚠", "🛰️", "🚀", "🛸", "⏰", "🌙", "🌡️", "☀️", "🪐", "🌟", "🌀", "🌈", "☂️", "❄️", "☄️", + "🔥", "💧", "🌊", "🎃", "✨", "🎈", "🎉", "🎏", "🎀", "🎁", "🎟️", "🏆️", "⚽️", "🏀", "🏈", "🎾", + "🥏", "🏓", "🏸", "🤿", "🥌", "🎯", "🪀", "🪁", "🔮", "🎲", "🧩", "🎨", "🧵", "👕", "🧦", "👗", + "🩳", "🎒", "👟", "👑", "🧢", "💄", "💍", "💎", "📢", "🎶", "🎙️", "📻️", "🎷", "🪗", "🎸", "🎺", + "🎻", "🪕", "🥁", "☎️", "🔋", "💿️", "🧮", "🎬️", "💡", "🔦", "🏮", "📕", "🏷️", "💳️", "✏️", "🖌️", + "🖍️", "📌", "📎", "🔑", "🪃", "🏹", "⚖️", "🧲", "🧪", "🧬", "🔬", "🔭", "📡", "🪑", "🧹", "🗿", + ]; + /* eslint-enable */ + + // FNV-1a implementation that is compatible with other languages + const prime = 0x01000193; + const offset = 0x811c9dc5; + let hash = offset; + const encoder = new TextEncoder(); + for (const byte of encoder.encode(bridgeString)) { + hash = Math.imul(hash ^ byte, 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 000000000000..82738723ae21 --- /dev/null +++ b/browser/components/torpreferences/content/connectionPane.xhtml @@ -0,0 +1,194 @@ +<!-- 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" + hidden="true"> + <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> + <description flex="1" id="torPreferences-currentBridges-description"> + <html:span id="torPreferences-currentBridges-descriptionText"/> + </description> + <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"> + <html:div class="torPreferences-bridgeCard-qr stop-click"> + <html:div class="torPreferences-bridgeCard-qrCode"/> + <html:div class="torPreferences-bridgeCard-qrOnionBox"/> + <html:div class="torPreferences-bridgeCard-qrOnion"/> + </html:div> + </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: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/connectionSettingsDialog.jsm b/browser/components/torpreferences/content/connectionSettingsDialog.jsm new file mode 100644 index 000000000000..dd7180678e98 --- /dev/null +++ b/browser/components/torpreferences/content/connectionSettingsDialog.jsm @@ -0,0 +1,397 @@ +"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(); + }); + + // Hack: see the CSS + this._dialog.style.minWidth = "0"; + this._dialog.style.minHeight = "0"; + } + + // 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 000000000000..8265cdd71c2f --- /dev/null +++ b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml @@ -0,0 +1,60 @@ +<?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> + <!-- Local Proxy --> + <checkbox id="torPreferences-connection-toggleProxy" label="​"/> + <box id="torPreferences-connection-grid"> + <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> + </box> + <!-- Firewall --> + <checkbox id="torPreferences-connection-toggleFirewall" label="​"/> + <box id="torPreferences-connection-firewall"> + <hbox class="indent" align="center"> + <label id="torPreferences-connection-allowedPorts"/> + </hbox> + <hbox id="torPreferences-connection-hboxAllowedPorts" 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 000000000000..e1689b5e6d64 --- /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 000000000000..33ee8e023bfd --- /dev/null +++ b/browser/components/torpreferences/content/provideBridgeDialog.jsm @@ -0,0 +1,67 @@ +"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(onSubmit) { + this.onSubmit = onSubmit; + this._dialog = null; + this._textarea = null; + } + + static get selectors() { + return { + header: "#torPreferences-provideBridge-header", + textarea: "#torPreferences-provideBridge-textarea", + }; + } + + _populateXUL(window, 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.source == TorBridgeSource.UserProvided) { + this._textarea.value = TorSettings.bridges.bridge_strings.join("\n"); + } + + this._dialog.addEventListener("dialogaccept", e => { + this.onSubmit(this._textarea.value); + }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + TorStrings.settings.learnMoreBridgesURL, + "tab" + ); + }); + } + + 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/provideBridgeDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/provideBridgeDialog.xhtml b/browser/components/torpreferences/content/provideBridgeDialog.xhtml new file mode 100644 index 000000000000..28d19cadaf9c --- /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 000000000000..eff4c2c083ef --- /dev/null +++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm @@ -0,0 +1,206 @@ +"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(onSubmit) { + this.onSubmit = onSubmit; + this._dialog = null; + this._submitButton = null; + this._dialogHeader = null; + this._captchaImage = null; + this._captchaEntryTextbox = null; + this._captchaRefreshButton = null; + this._incorrectCaptchaHbox = null; + this._incorrectCaptchaLabel = null; + } + + 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._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( + TorStrings.settings.learnMoreBridgesURL, + "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.onSubmit(aBridges); + this._submitButton.disabled = false; + // This was successful, but use cancelDialog() to close, since + // we intercept the `dialogaccept` event. + this._dialog.cancelDialog(); + } else { + 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._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) { + gSubDialog.open( + "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml", + { + features: "resizable=yes", + closingCallback: () => { + this.close(); + }, + }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml new file mode 100644 index 000000000000..be9d310008d7 --- /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://global/skin/icons/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 000000000000..94a57b9b165e --- /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 000000000000..9c17f8132978 --- /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 000000000000..388d8bb4c152 --- /dev/null +++ b/browser/components/torpreferences/content/torPreferences.css @@ -0,0 +1,714 @@ +@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://browser/content/torpreferences/network.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://browser/content/torpreferences/check.svg"); + -moz-context-properties: fill; + fill: var(--purple-60); +} + +@media (prefers-color-scheme: dark) { + #torPreferences-status-internet-statusIcon.online, #torPreferences-status-tor-statusIcon.connected { + fill: var(--purple-30); + } +} + +#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; +} + +#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; +} + +#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) 0% 75%, 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; + cursor: pointer; +} + +.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); +} + +.torPreferences-bridgeCard:hover { + background: var(--in-content-box-background); + box-shadow: var(--card-shadow-hover); +} + +.single-card .torPreferences-bridgeCard, +.torPreferences-bridgeCard.currently-connected { + cursor: default; +} + +.torPreferences-bridgeCard-heading { + display: flex; + align-items: center; +} + +.torPreferences-bridgeCard-id { + display: flex; + align-items: center; + font-weight: 700; +} + +.torPreferences-bridgeCard-id .emoji { + width: 20px; + height: 20px; + margin-inline-start: 4px; + padding: 4px; + font-size: 20px; + border-radius: 4px; + background: var(--in-content-box-background-odd); +} + +.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; + overflow: hidden; + color: var(--in-content-deemphasized-text); + white-space: nowrap; + text-overflow: ellipsis; +} + +.expanded .torPreferences-bridgeCard-headingAddr, +.currently-connected .torPreferences-bridgeCard-headingAddr, +.single-card .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; + margin-inline-end: 12px; + color: var(--purple-60); +} + +@media (-moz-toolbar-prefers-color-scheme: dark) { + .torPreferences-bridgeCard-connectedBadge { + color: var(--purple-30); + } +} + +.currently-connected .torPreferences-bridgeCard-connectedBadge { + display: flex; +} + +.torPreferences-bridgeCard-connectedIcon { + margin-inline-start: 1px; + margin-inline-end: 7px; + list-style-image: url("chrome://browser/content/torpreferences/check.svg"); + -moz-context-properties: fill; + fill: currentColor; +} + +.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-menu menuitem { + fill: currentColor; + -moz-context-properties: fill; +} + +.torPreferences-bridgeCard-qrWrapper { + grid-area: bridge-qr; + display: block; /* So it doesn't stretch the child vertically. */ + margin-inline-end: 14px; +} + +.torPreferences-bridgeCard-qr { + --qr-one: black; + --qr-zero: white; + background: var(--qr-zero); + position: relative; + padding: 4px; + border-radius: 2px; +} + +.torPreferences-bridgeCard-qrCode { + width: 112px; + height: 112px; + /* Define these colors, as they will be passed to the QR code library */ + background: var(--qr-zero); + color: var(--qr-one); +} + +.torPreferences-bridgeCard-qrOnionBox { + width: 28px; + height: 28px; + position: absolute; + top: calc(50% - 14px); + inset-inline-start: calc(50% - 14px); + background: var(--qr-zero); +} + +.torPreferences-bridgeCard-qrOnion { + width: 16px; + height: 16px; + position: absolute; + top: calc(50% - 8px); + inset-inline-start: calc(50% - 8px); + + mask: url("chrome://browser/skin/onion.svg"); + mask-repeat: no-repeat; + mask-size: 16px; + background: var(--qr-one); +} + +.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnionBox { + background: var(--qr-one); +} + +.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnion { + mask: url("chrome://global/skin/icons/search-glass.svg"); + background: var(--qr-zero); +} + +.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, +.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; +} + +.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; +} + +input.torPreferences-bridgeCard-addr { + width: 100%; + color: var(--in-content-deemphasized-text); +} + +.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; +} + +#torPreferences-requestBridge-refreshCaptchaButton image { + -moz-context-properties: fill; + fill: currentColor; +} + +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 { + /* Some readers don't recognize QRs with inverted colors, so let's make + the ones are darker than zeroes. See + https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41049 */ + --qr-one: black; + --qr-zero: white; + background: var(--qr-zero); + position: relative; + /* Padding is needed in case the dark theme is used so the bits don't blend + with whatever the default background color is. */ + padding: 10px; + margin: auto; + margin-bottom: 20px; + max-width: max-content; + border-radius: 5px; +} + +#bridgeQr-target { + width: 300px; + height: 300px; + background: var(--qr-zero); + color: var(--qr-one); +} + +#bridgeQr-onionBox { + position: absolute; + width: 70px; + height: 70px; + top: calc(50% - 35px); + left: calc(50% - 35px); + background-color: var(--qr-zero); +} + +#bridgeQr-onion { + position: absolute; + width: 38px; + height: 38px; + top: calc(50% - 19px); + left: calc(50% - 19px); + mask: url("chrome://browser/skin/onion.svg"); + mask-repeat: no-repeat; + mask-size: 38px; + background: var(--qr-one); +} + +/* Builtin bridge dialog */ +#torPreferences-builtinBridge-dialog { + /* Hack: ask XUL to load with a lot of space, then we will remove the minimum + sizes after we populate the string. Users with high resolutions should see a + big dialog, but with all the needed buttons, users with small resolutions + should see scrollbars. + Known problems: + 1) the hack works only if a window is *already* small: if it is big, and then + it is resized, the dialog will not shrink; + 2) horizontal scrollbars are never added. + The proper solution for this will be loading the localized string with + Mozilla's way. + See also https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41044 */ + min-width: 700px; + min-height: 550px; +} + +#torPreferences-builtinBridge-header { + margin: 8px 0 10px 0; +} + +#torPreferences-builtinBridge-description { + margin-bottom: 18px; +} + +#torPreferences-builtinBridge-typeSelection { + margin-bottom: 16px; +} + +#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-dialog { + /* Hack: like the built-in dialog */ + min-width: 700px; + min-height: 550px; +} + +#torPreferences-connection-header { + margin: 4px 0 14px 0; +} + +#torPreferences-connection-grid { + display: grid; + grid-template-columns: auto 1fr; +} + +#torPreferences-localProxy-textboxAddress, +#torPreferences-localProxy-textboxUsername, +#torPreferences-localProxy-textboxPassword, +#torPreferences-connection-textboxAllowedPorts { + -moz-box-flex: 1; +} + +#torPreferences-connection-firewall { + display: flex; +} + +#torPreferences-connection-hboxAllowedPorts { + 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; +} + +/* 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); +} diff --git a/browser/components/torpreferences/content/torPreferencesIcon.svg b/browser/components/torpreferences/content/torPreferencesIcon.svg new file mode 100644 index 000000000000..382a061774aa --- /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 000000000000..b54373ae7bc2 --- /dev/null +++ b/browser/components/torpreferences/jar.mn @@ -0,0 +1,22 @@ +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/check.svg (content/check.svg) + 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) + content/browser/torpreferences/bridgemoji/ (content/bridgemoji/*.svg) + content/browser/torpreferences/bridgemoji-annotations.json (content/bridgemoji-annotations.json) diff --git a/browser/components/torpreferences/moz.build b/browser/components/torpreferences/moz.build new file mode 100644 index 000000000000..2661ad7cb9f3 --- /dev/null +++ b/browser/components/torpreferences/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/tools/torbrowser/update_bridgemoiji.py b/tools/torbrowser/update_bridgemoiji.py new file mode 100755 index 000000000000..af555eee18d3 --- /dev/null +++ b/tools/torbrowser/update_bridgemoiji.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# Script to update bridgemoji files from Twemoji, and from Unicode +# datasets. +# Please be sure to grab and extract twe-svg.zip from +# https://github.com/mozilla/twemoji-colr, and to get the latest +# version of https://github.com/unicode-org/cldr.git. + +import json +from pathlib import Path +from shutil import copyfile +import sys +from xml.dom.minidom import parse + + +if len(sys.argv) < 3: + print(f'Usage: {sys.argv[0]} twemoji-svg-dir cldr-dir') + sys.exit(1) +twemoji_dir = Path(sys.argv[1]) +cldr_dir = Path(sys.argv[2]) + +LANGS = [ + "ar", + "ca", + "cs", + "da", + "de", + "el", + "en", + "es", + "fa", + "fr", + "ga", + "he", + "hu", + "id", + "is", + "it", + "ja", + "ka", + "ko", + "lt", + "mk", + "ms", + "my", + # "nb", # Empty file, currently!! + "nl", + "pl", + "pt", + "ro", + "ru", + "sv", + "th", + "tr", + "uk", + "vi", + "zh", # zh-CN, zh-hans + "zh_Hant", # zh-TW, zh-hant +] + +# Currently the script is in tools/torbrowser/ +firefox_root = Path(__file__).parents[2] +panel_dir = firefox_root / 'browser/components/torpreferences/content' + +with (panel_dir / 'connectionPane.js').open() as f: + pane_js = f.read() +make_id_offset = pane_js.find('function makeBridgeId(bridgeString) {') +emojis_var = 'const emojis = ' +emojis_offset = pane_js.find(emojis_var, make_id_offset) + len(emojis_var) +close_offset = pane_js.find(']', emojis_offset) +emojis_str = pane_js[emojis_offset:close_offset].strip('\t \n,') +emojis_str += ']' +emojis = json.loads(emojis_str) +codepoints = [] +for idx, e in enumerate(emojis): + if len(e) > 2 or (len(e) == 2 and ord(e[1]) != 0xfe0f): + # U+FE0F is "VARIATION SELECTOR-16" and tells the emoji to be + # colored, or something like that. + print(f'Unsupported emoji {e}: too many codepoints') + sys.exit(2) + codepoints.append(ord(e[0])) + +emojis_dest = panel_dir / 'bridgemoji' +emojis_dest.mkdir(exist_ok=True) +for f in emojis_dest.iterdir(): + f.unlink() +for cp in codepoints: + src = twemoji_dir / f'{cp:x}.svg' + dst = emojis_dest / f'{cp:x}.svg' + copyfile(src, dst) + +data = {l: {} for l in LANGS} + +for l in LANGS: + with (cldr_dir / f'common/annotations/{l}.xml').open() as f: + doc = parse(f) + anns = doc.getElementsByTagName('annotation') + for ann in anns: + cp = ann.getAttribute('cp') + if len(cp) != 1: + continue + try: + idx = codepoints.index(ord(cp)) + if ann.getAttribute('type') == 'tts': + ann.normalize() + data[l][emojis[idx]] = ann.firstChild.data + except ValueError: + pass + if len(data[l]) != len(emojis): + print(f'Lang {l} doesn't have all the emoji descriptions!') + +data['zh-CN'] = data.pop('zh') +data['zh-TW'] = data.pop('zh_Hant') + +with (panel_dir / 'bridgemoji-annotations.json').open('w') as f: + json.dump(data, f, ensure_ascii=False, indent=2)
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit ebd7d522fcd8f5cda1728e6235951af2e844aa6b 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 | 6 + 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 | 17 + browser/components/BrowserGlue.jsm | 14 + browser/components/about/AboutRedirector.cpp | 5 + browser/components/about/components.conf | 1 + browser/components/moz.build | 1 + browser/components/torconnect/TorConnectChild.jsm | 9 + browser/components/torconnect/TorConnectParent.jsm | 202 +++++ .../torconnect/content/aboutTorConnect.css | 324 ++++++++ .../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 | 95 +++ .../torconnect/content/torconnect-urlbar.css | 37 + .../torconnect/content/torconnect-urlbar.inc.xhtml | 10 + browser/components/torconnect/jar.mn | 13 + browser/components/torconnect/moz.build | 6 + browser/components/urlbar/UrlbarInput.jsm | 37 + browser/modules/TorProcessService.jsm | 12 + browser/modules/moz.build | 2 + browser/themes/shared/browser-shared.css | 1 + dom/base/Document.cpp | 49 +- dom/base/nsGlobalWindowOuter.cpp | 2 + toolkit/actors/AboutHttpsOnlyErrorParent.jsm | 5 + .../components/httpsonlyerror/content/errorpage.js | 19 +- .../processsingleton/MainProcessSingleton.jsm | 1 + toolkit/modules/RemotePageAccessManager.jsm | 26 + .../lib/environments/browser-window.js | 4 + 38 files changed, 1894 insertions(+), 29 deletions(-)
diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm index 315957ff112e..250b95b95059 100644 --- a/browser/actors/NetErrorParent.jsm +++ b/browser/actors/NetErrorParent.jsm @@ -18,6 +18,8 @@ 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.", @@ -341,6 +343,10 @@ class NetErrorParent extends JSWindowActorParent { break; } } + break; + case "ShouldShowTorConnect": + return TorConnect.shouldShowTorConnect; } + return undefined; } } diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 5b2b12d79922..c0ba0466fb5f 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", @@ -646,6 +647,7 @@ var gPageIcons = {
var gInitialPages = [ "about:tor", + "about:torconnect", "about:blank", "about:home", ...(AppConstants.NIGHTLY_BUILD ? ["about:firefoxview"] : []), @@ -1874,6 +1876,8 @@ var gBrowserInit = { }
this._loadHandled = true; + + TorBootstrapUrlbar.init(); },
_cancelDelayedStartup() { @@ -2421,32 +2425,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 = (aUri => { + // If the given URI is different from the homepage, we want to load it. + if (aUri != defaultArgs) { + AboutNewTab.noteNonDefaultStartup(); + + if (aUri instanceof Ci.nsIArray) { + // Transform the nsIArray of nsISupportsString's into a JS Array of + // JS strings. + return Array.from( + aUri.enumerate(Ci.nsISupportsString), + supportStr => supportStr.data + ); + } else if (aUri instanceof Ci.nsISupportsString) { + return aUri.data; + } + return aUri; + }
- 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(aUri => { + if (aUri == null) { + aUri = []; + }
- // 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; + aUri = TorConnect.getURIsToLoad(aUri); + return aUri; + }); } - return willOverride.then(willOverrideHomepage => - willOverrideHomepage ? null : uri - ); + return uri; })()); },
@@ -2513,6 +2533,8 @@ var gBrowserInit = {
NewIdentityButton.uninit();
+ TorBootstrapUrlbar.uninit(); + gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) { diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index cb29fa7bd280..32d971c93d6d 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 @@ -122,6 +123,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 43a1777a8b41..1714b1b8a4be 100644 --- a/browser/base/content/certerror/aboutNetError.js +++ b/browser/base/content/certerror/aboutNetError.js @@ -230,7 +230,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 @@ -253,6 +253,16 @@ function initPage() { document.body.classList.add("blocked"); }
+ // 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}`); + } + // Only worry about captive portals if this is a cert error. let showCaptivePortalUI = isCaptive() && gIsCertError; if (showCaptivePortalUI) { diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 6123e1336aed..81479615dc72 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -323,6 +323,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 977f40e5e331..90e795888a15 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", () => @@ -259,6 +260,22 @@ 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 b1a0fc5ffe44..0092d25948e7 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -740,6 +740,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 7c915f2b45b0..0a495a223e3e 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -133,6 +133,11 @@ 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 | + nsIAboutModule::IS_SECURE_CHROME_UI}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 56226324bc32..6095653409cd 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -28,6 +28,7 @@ pages = [ 'robots', 'sessionrestore', 'tabcrashed', + 'torconnect', 'unloads', 'welcome', 'welcomeback', diff --git a/browser/components/moz.build b/browser/components/moz.build index 0fa76a0e7038..ab1dac40dfab 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -58,6 +58,7 @@ DIRS += [ "translation", "uitour", "urlbar", + "torconnect", "torpreferences", ]
diff --git a/browser/components/torconnect/TorConnectChild.jsm b/browser/components/torconnect/TorConnectChild.jsm new file mode 100644 index 000000000000..bd6dd549f156 --- /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 000000000000..cf3c1233dd62 --- /dev/null +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -0,0 +1,202 @@ +// 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 { + 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 +*/ + +class TorConnectParent extends JSWindowActorParent { + constructor(...args) { + super(...args); + + const self = this; + + this.state = { + State: TorConnect.state, + StateChanged: false, + PreviousState: TorConnectState.Initial, + ErrorMessage: TorConnect.errorMessage, + ErrorDetails: TorConnect.errorDetails, + 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, + }; + + // 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.PreviousState = self.state.State; + 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; + } + self.state.HasBootsrapEverFailed = + TorConnect.hasBootstrapEverFailed; + 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.InternetStatus = TorConnect.internetStatus; + self.state.DetectedLocation = TorConnect.detectedLocation; + 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 + ); + + 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() { + // 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 + ); + Services.obs.removeObserver(this.userActionObserver, BroadcastTopic); + } + + 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:cancel-bootstrap": + TorConnect.cancelBootstrap(); + break; + case "torconnect:begin-bootstrap": + TorConnect.beginBootstrap(); + break; + 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, + InternetStatus, + 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 000000000000..5930f0948291 --- /dev/null +++ b/browser/components/torconnect/content/aboutTorConnect.css @@ -0,0 +1,324 @@ + +/* 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; +} + +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) { + 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); +} + +#breadcrumbs.hidden { + visibility: hidden; +} + +.breadcrumb-item, .breadcrumb-separator { + 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(--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 { + 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; + height: 16px; + list-style-position: inside; + fill: currentColor; + -moz-context-properties: fill; +} + +.breadcrumb-item.active { + color: var(--in-content-accent-color); +} + +.breadcrumb-item.disabled, .breadcrumb-item.disabled:hover, .breadcrumb-item.disabled:active { + color: var(--in-content-text-color); + opacity: 0.4; + 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); +} + +.breadcrumb-item.hidden, .breadcrumb-separator.hidden { + display: none; +} + +#connect-to-tor { + margin-inline-start: 0; +} + +#connect-to-tor-icon { + list-style-image: url("chrome://browser/content/torconnect/onion.svg"); +} + +#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 { + --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; +} + +#locationDropdownLabel { + margin-block: auto; + margin-inline: 4px; +} + +#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; + /* 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) { + #tryAgainButton { + margin-top: 4px; + } +} + +form#locationDropdown { + width: 240px; +} + +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; +} + +:root { + --progressbar-shadow-start: rgba(255, 255, 255, 0.7); + --progressbar-gradient: linear-gradient(90deg, #FC00FF 0%, #00DBDE 50%, #FC00FF 100%); +} + +@media (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: 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: 100%; + max-width: 45em; +} + +#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.offline, .title.assist, .title.final { + 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 000000000000..6b33442c7d02 --- /dev/null +++ b/browser/components/torconnect/content/aboutTorConnect.js @@ -0,0 +1,843 @@ +// Copyright (c) 2021, The Tor Project, Inc. + +/* eslint-env mozilla/frame-script */ + +// populated in AboutTorConnect.init() +let TorStrings = {}; +let TorConnectState = {}; +let InternetStatus = {}; + +const UIStates = Object.freeze({ + ConnectToTor: "ConnectToTor", + Offline: "Offline", + ConnectionAssist: "ConnectionAssist", + CouldNotLocate: "CouldNotLocate", + LocationConfirm: "LocationConfirm", + FinalError: "FinalError", +}); + +const BreadcrumbStatus = Object.freeze({ + Hidden: "hidden", + Disabled: "disabled", + Default: "default", + Active: "active", + Error: "error", +}); + +class AboutTorConnect { + selectors = Object.freeze({ + textContainer: { + title: "div.title", + titleText: "h1.title-text", + longContentText: "#connectLongContentText", + }, + progress: { + description: "p#connectShortDescText", + 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", + }, + tryBridge: { + separator: "#try-bridge-separator", + link: "#try-bridge", + label: "#try-bridge .breadcrumb-label", + }, + }, + viewLog: { + container: "#viewLogContainer", + 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: "#locationDropdownLabel", + locationDropdown: "form#locationDropdown", + locationDropdownSelect: "form#locationDropdown select", + }, + }); + + 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 + ), + 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 + ), + tryBridgeSeparator: document.querySelector( + this.selectors.breadcrumbs.tryBridge.separator + ), + tryBridgeLink: document.querySelector( + this.selectors.breadcrumbs.tryBridge.link + ), + 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 + ), + 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), + locationDropdownLabel: document.querySelector( + this.selectors.buttons.locationDropdownLabel + ), + locationDropdown: document.querySelector( + this.selectors.buttons.locationDropdown + ), + locationDropdownSelect: document.querySelector( + this.selectors.buttons.locationDropdownSelect + ), + 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() { + RPMSendAsyncMessage("torconnect:begin-bootstrap"); + } + + beginAutoBootstrap(countryCode) { + if (countryCode === "automatic") { + countryCode = ""; + } + RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode); + } + + cancelBootstrap() { + 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) { + element.classList.toggle("primary", primary !== undefined && primary); + element.removeAttribute("hidden"); + } + + hide(element) { + element.setAttribute("hidden", "true"); + } + + 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.locationDropdownLabel); + this.hide(this.elements.locationDropdown); + this.hide(this.elements.tryBridgeButton); + } + + 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 + ); + } + + populateFrequentLocations(locations) { + this.removeFrequentLocations(); + if (!locations || !locations.length) { + return; + } + + const locationNodes = []; + 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); + } + // locale sort by name + locationNodes.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + + 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, + locationGroup + ); + } + + removeFrequentLocations() { + const select = this.elements.locationDropdownSelect; + for (const option of select.querySelectorAll(".frequent-location")) { + option.remove(); + } + } + + validateLocation() { + const selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + const selectedOption = this.elements.locationDropdownSelect.options[ + selectedIndex + ]; + if (!selectedOption.value) { + this.elements.tryBridgeButton.setAttribute("disabled", "disabled"); + } else { + this.elements.tryBridgeButton.removeAttribute("disabled"); + } + } + + setTitle(title, className) { + this.elements.titleText.textContent = title; + this.elements.title.className = "title"; + 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(connectToTor, connectionAssist, tryBridge) { + this.elements.breadcrumbContainer.classList.remove("hidden"); + 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, 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); + }); + } + + 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) { + this.showConnectToTor(state); + } + + update_Configuring(state) { + 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) { + this.showBootstrapping(state); + } + + update_Bootstrapping(state) { + this.showBootstrapping(state); + } + + update_Error(state) { + 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) { + 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); + } + } else { + console.error( + "We received an error starting from an unexpected state", + state + ); + } + } + + 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) + } + + 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); + if (state.HasBootsrapEverFailed) { + this.setBreadcrumbsStatus(...breadcrumbs); + } else { + this.hideBreadcrumbs(); + } + 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(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.restartButton); + this.show(this.elements.configureButton, 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"); + }); + if (pieces.length > 1) { + const first = pieces.shift(); + this.setLongText(first, link, ...pieces); + } else { + this.setLongText(text); + } + } + + showLocationForm(isError, buttonLabel) { + this.hideButtons(); + RPMSendQuery("torconnect:get-country-codes").then(codes => { + if (codes && codes.length) { + this.populateFrequentLocations(codes); + this.setLocation(); + } + }); + let firstOpt = this.elements.locationDropdownSelect.options[0]; + if (this.uiState.allowAutomaticLocation) { + firstOpt.value = "automatic"; + firstOpt.textContent = TorStrings.torConnect.automatic; + } else { + firstOpt.value = ""; + firstOpt.textContent = TorStrings.torConnect.selectCountryRegion; + } + this.setLocation(); + this.validateLocation(); + this.show(this.elements.locationDropdownLabel); + this.show(this.elements.locationDropdown); + 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; + } + } + + getLocation() { + const selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + return this.elements.locationDropdownSelect.options[selectedIndex].value; + } + + setLocation(code) { + if (!code) { + code = this.uiState.selectedLocation; + } else { + this.uiState.selectedLocation = code; + } + if (this.getLocation() === code) { + 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 === code) { + 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( + 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.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"); + }); + + 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.uiState.selectedLocation = this.getLocation(); + this.saveUIState(); + this.validateLocation(); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + location: this.uiState.selectedLocation, + }); + }); + + this.elements.locationDropdownLabel.textContent = + TorStrings.torConnect.yourLocation; + + 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); + } + }); + } + + initObservers() { + // TorConnectParent feeds us state blobs to we use to update our UI + RPMAddMessageListener("torconnect:state-change", ({ data }) => { + this.updateUI(data); + }); + RPMAddMessageListener("torconnect:user-action", ({ data }) => { + if (data.location) { + this.uiState.selectedLocation = data.location; + this.setLocation(); + } + if (data.uiState !== undefined) { + this.transitionUIState(data.uiState, data.connState); + } + }); + } + + 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); + 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); + } +} + +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 000000000000..77d2e6889570 --- /dev/null +++ b/browser/components/torconnect/content/aboutTorConnect.xhtml @@ -0,0 +1,68 @@ +<!-- 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="progressBar"> + <div id="progressBackground" /> + <div id="progressSolid" /> + </div> + <div id="connectPageContainer" class="container"> + <div id="breadcrumbs" class="hidden"> + <span id="connect-to-tor" class="breadcrumb-item"> + <span id="connect-to-tor-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + <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 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"/> + </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"></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> + <label id="locationDropdownLabel" for="countries"/> + <form id="locationDropdown" hidden="true"> + <select id="countries"> + </select> + </form> + <button id="tryBridgeButton" 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 000000000000..3f6d8ded52be --- /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 000000000000..5ae3f05dfd08 --- /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 000000000000..8f2005e36055 --- /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 000000000000..1e5c41ccf99a --- /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/onion-slash-fillable.svg b/browser/components/torconnect/content/onion-slash-fillable.svg new file mode 100644 index 000000000000..18f1c5a5520b --- /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 000000000000..93eb24b03905 --- /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 000000000000..7655a800d9ee --- /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 000000000000..61b15b809ef6 --- /dev/null +++ b/browser/components/torconnect/content/torBootstrapUrlbar.js @@ -0,0 +1,95 @@ +// 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"); + +/* globals browser, gURLBar, Services */ + +var TorBootstrapUrlbar = { + selectors: Object.freeze({ + torConnect: { + box: "hbox#torconnect-box", + label: "label#torconnect-label", + }, + }), + + elements: null, + + updateTorConnectBox(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(aSubject, aTopic, aData) { + if (aTopic === TorConnectTopics.StateChange) { + const obj = aSubject?.wrappedJSObject; + this.updateTorConnectBox(obj?.state); + } + }, + + init() { + 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() { + 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 000000000000..96d9ff63ec71 --- /dev/null +++ b/browser/components/torconnect/content/torconnect-urlbar.css @@ -0,0 +1,37 @@ +/* + ensure our torconnect button is always visible (same rule as for the bookmark button) +*/ +label#torconnect-label { + margin: 0; + opacity: 0.6; + padding: 0 0.5em; +} + +hbox.urlbar-page-action#torconnect-box { + display: -moz-inline-box!important; +} + +/* 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 000000000000..60e985a72691 --- /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 000000000000..4a65177f7eae --- /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/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) + skin/classic/browser/torconnect-urlbar.css (content/torconnect-urlbar.css) diff --git a/browser/components/torconnect/moz.build b/browser/components/torconnect/moz.build new file mode 100644 index 000000000000..eb29c31a4243 --- /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 20c7f9fa2c21..ce5bf88df509 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -10,6 +10,38 @@ 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", @@ -2547,6 +2579,11 @@ 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 000000000000..201e331b2806 --- /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 018ca1a9430b..98caa91ee6f4 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -146,6 +146,8 @@ EXTRA_JS_MODULES += [ "SitePermissions.jsm", "TabsList.jsm", "TabUnloader.jsm", + "TorConnect.jsm", + "TorProcessService.jsm", "TorProtocolService.jsm", "TorSettings.jsm", "TorStrings.jsm", diff --git a/browser/themes/shared/browser-shared.css b/browser/themes/shared/browser-shared.css index 1a1bb759b8b4..c8dac0afb49a 100644 --- a/browser/themes/shared/browser-shared.css +++ b/browser/themes/shared/browser-shared.css @@ -21,6 +21,7 @@ @import url("chrome://browser/skin/ctrlTab.css"); @import url("chrome://browser/skin/customizableui/customizeMode.css"); @import url("chrome://browser/skin/UITour.css"); +@import url("chrome://browser/skin/torconnect-urlbar.css");
@namespace html url("http://www.w3.org/1999/xhtml");
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index ad3abe7bffec..430238686cd0 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -17795,8 +17795,55 @@ ColorScheme Document::DefaultColorScheme() const { }
ColorScheme Document::PreferredColorScheme(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)) { + nsContentUtils::ShouldResistFingerprinting(this) && + !documentUsesPreferredColorScheme(this)) { return ColorScheme::Light; }
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index d98276d975b7..03b90e6098d8 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -6107,6 +6107,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 1094126816f6..1d34525baf5c 100644 --- a/toolkit/actors/AboutHttpsOnlyErrorParent.jsm +++ b/toolkit/actors/AboutHttpsOnlyErrorParent.jsm @@ -12,6 +12,8 @@ const { PrivateBrowsingUtils } = ChromeUtils.import( ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TorConnect } = ChromeUtils.import("resource:///modules/TorConnect.jsm"); + class AboutHttpsOnlyErrorParent extends JSWindowActorParent { get browser() { return this.browsingContext.top.embedderElement; @@ -22,7 +24,10 @@ class AboutHttpsOnlyErrorParent extends JSWindowActorParent { case "goBack": this.goBackFromErrorPage(this.browser); break; + case "ShouldShowTorConnect": + return TorConnect.shouldShowTorConnect; } + return undefined; }
goBackFromErrorPage(aBrowser) { diff --git a/toolkit/components/httpsonlyerror/content/errorpage.js b/toolkit/components/httpsonlyerror/content/errorpage.js index 50ece6e33ba1..3eec71861304 100644 --- a/toolkit/components/httpsonlyerror/content/errorpage.js +++ b/toolkit/components/httpsonlyerror/content/errorpage.js @@ -122,8 +122,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 28a16b732172..4afa7f118a72 100644 --- a/toolkit/components/processsingleton/MainProcessSingleton.jsm +++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm @@ -22,6 +22,7 @@ MainProcessSingleton.prototype = {
// FIXME: Is this import really necessary? ChromeUtils.import("resource:///modules/TorSettings.jsm"); + ChromeUtils.import("resource:///modules/TorConnect.jsm");
Services.ppmm.loadProcessScript( "chrome://global/content/process-content.js", diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index 2ae489873680..6b1da814765f 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -66,6 +66,7 @@ let RemotePageAccessManager = { RPMAddMessageListener: ["WWWReachable"], RPMTryPingSecureWWWLink: ["*"], RPMOpenSecureWWWLink: ["*"], + RPMSendQuery: ["ShouldShowTorConnect"], }, "about:certificate": { RPMSendQuery: ["getCertificates"], @@ -94,6 +95,7 @@ let RemotePageAccessManager = { RPMAddToHistogram: ["*"], RPMGetInnerMostURI: ["*"], RPMGetHttpResponseHeader: ["*"], + RPMSendQuery: ["ShouldShowTorConnect"], }, "about:plugins": { RPMSendQuery: ["RequestPlugins"], @@ -214,6 +216,30 @@ let RemotePageAccessManager = { RPMAddMessageListener: ["*"], RPMRemoveMessageListener: ["*"], }, + "about:tbupdate": { + RPMSendQuery: ["FetchUpdateData"], + }, + "about:torconnect": { + RPMAddMessageListener: [ + "torconnect:state-change", + "torconnect:user-action", + ], + RPMSendAsyncMessage: [ + "torconnect:open-tor-preferences", + "torconnect:begin-bootstrap", + "torconnect:begin-autobootstrap", + "torconnect:cancel-bootstrap", + "torconnect:set-quickstart", + "torconnect:view-tor-logs", + "torconnect:restart", + "torconnect:set-ui-state", + "torconnect:broadcast-user-action", + ], + RPMSendQuery: [ + "torconnect:get-init-args", + "torconnect:get-country-codes", + ], + }, },
/** 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 de0091ae8d4d..962ca7a810a3 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 @@ -74,6 +74,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit d6914de964140fe229ee83d3c7bd8c53c44c7b48 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 000000000000..bc09839f9f05 --- /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 000000000000..ee3927a9e7c4 --- /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 000000000000..f481660f1417 --- /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 000000000000..8bb12f360e5e --- /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 000000000000..16e41344a3c4 --- /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 000000000000..cc5172733bbe --- /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 000000000000..01db60b9c28a --- /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 000000000000..c0a956e9f687 --- /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 1241f1b0f94e..3873aec36f63 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -89,7 +89,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit e46aac3e9373f7f5272e10c6b8cc3fc3ee3f4f8d 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 | 14 +- browser/components/BrowserContentHandler.jsm | 35 ++- .../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 | 31 +- toolkit/mozapps/extensions/AddonManager.jsm | 25 ++ toolkit/mozapps/extensions/test/browser/head.js | 1 + .../extensions/test/xpcshell/head_addons.js | 1 + toolkit/mozapps/update/UpdateService.jsm | 200 ++++++++++--- toolkit/mozapps/update/UpdateServiceStub.jsm | 4 + toolkit/mozapps/update/common/updatehelper.cpp | 8 + toolkit/mozapps/update/updater/launchchild_osx.mm | 2 + toolkit/mozapps/update/updater/moz.build | 2 +- toolkit/mozapps/update/updater/updater.cpp | 325 ++++++++++++++++++--- toolkit/xre/MacLaunchHelper.h | 2 + toolkit/xre/MacLaunchHelper.mm | 2 + toolkit/xre/nsAppRunner.cpp | 22 +- toolkit/xre/nsUpdateDriver.cpp | 130 ++++++++- 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 ++++- 30 files changed, 905 insertions(+), 178 deletions(-)
diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in index 8dd3a9a65661..3a5550c96c15 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 70e71c0b993e..b86b202646cd 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -140,14 +140,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 b29726c1aab2..d75c5444b84f 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 69e7c510b305..458a52201e82 100644 --- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -54,15 +54,15 @@ 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 58edd37d9599..714a5fac8bf0 100644 --- a/browser/components/BrowserContentHandler.jsm +++ b/browser/components/BrowserContentHandler.jsm @@ -37,6 +37,9 @@ 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"; @@ -100,7 +103,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. @@ -117,6 +121,8 @@ function needHomepageOverride(prefb) {
var mstone = Services.appinfo.platformVersion;
+ var savedTBVersion = prefb.getCharPref(kTBSavedVersionPref, null); + var savedBuildID = prefb.getCharPref( "browser.startup.homepage_override.buildID", "" @@ -138,7 +144,21 @@ 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) { @@ -651,6 +671,10 @@ nsBrowserContentHandler.prototype = { "browser.startup.homepage_override.buildID", "unknown" ); + + // We do the same for the Tor Browser version. + let old_tbversion = prefb.getCharPref(kTBSavedVersionPref, null); + override = needHomepageOverride(prefb); if (override != OVERRIDE_NONE) { switch (override) { @@ -677,9 +701,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. @@ -687,6 +712,10 @@ 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 028dbf26e636..fdcfa732aa59 100644 --- a/browser/components/customizableui/content/panelUI.inc.xhtml +++ b/browser/components/customizableui/content/panelUI.inc.xhtml @@ -151,7 +151,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 6fa324be930b..83979ddf4d5b 100755 --- a/browser/confvars.sh +++ b/browser/confvars.sh @@ -5,26 +5,6 @@
MOZ_APP_VENDOR=Mozilla
-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 @@ -37,6 +17,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 84939f55d210..1cffb5564030 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 6df13230a45b..9a0b162d447d 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 81f500a0b752..a85df3393c16 100644 --- a/build/moz.configure/init.configure +++ b/build/moz.configure/init.configure @@ -957,7 +957,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(build_environment, build_project, version_path, "--help") @imports(_from="__builtin__", _import="open") @@ -1004,7 +1003,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 dda4efcdf8e1..e7405b21b61b 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 0eb7468e2847..dcf1bde3a574 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 10e17cf57375..06e349780fa3 100644 --- a/toolkit/modules/UpdateUtils.jsm +++ b/toolkit/modules/UpdateUtils.jsm @@ -87,7 +87,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": @@ -167,7 +167,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. * @@ -183,7 +184,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. * @@ -255,7 +257,10 @@ 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 @@ -324,7 +329,10 @@ 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); @@ -419,7 +427,10 @@ 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); @@ -555,14 +566,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 f525710f9053..424387dd02dd 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -40,6 +40,7 @@ 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_REMOTESETTINGS_DISABLED = "extensions.remoteSettings.disabled"; +const PREF_EM_LAST_TORBROWSER_VERSION = "extensions.lastTorBrowserVersion";
const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; @@ -644,6 +645,30 @@ 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 95321e7ad3bd..2e7a1312c540 100644 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -41,6 +41,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 88aa66c1c412..46d6651bcf4a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -386,6 +386,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 a81317f484ec..ddef19a3782c 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -12,6 +12,19 @@ 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, @@ -245,6 +258,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; @@ -662,6 +676,11 @@ function areDirectoryEntriesWriteable(aDir) { * @return true if elevation is required, false otherwise */ function getElevationRequired() { + if (AppConstants.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; + } if (AppConstants.platform != "macosx") { return false; } @@ -741,20 +760,22 @@ function getCanApplyUpdates() { return false; }
- if (AppConstants.platform == "macosx") { - LOG( - "getCanApplyUpdates - bypass the write since elevation can be used " + - "on Mac OS X" - ); - return true; - } + if (!AppConstants.TOR_BROWSER_UPDATE) { + if (AppConstants.platform == "macosx") { + LOG( + "getCanApplyUpdates - bypass the write since elevation can be used " + + "on Mac OS X" + ); + return true; + }
- if (shouldUseService()) { - LOG( - "getCanApplyUpdates - bypass the write checks because the Windows " + - "Maintenance Service can be used" - ); - return true; + if (shouldUseService()) { + LOG( + "getCanApplyUpdates - bypass the write checks because the Windows " + + "Maintenance Service can be used" + ); + return true; + } }
try { @@ -1567,28 +1588,32 @@ function handleUpdateFailure(update, errorCode) { cancelations++; Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations); if (AppConstants.platform == "macosx") { - let osxCancelations = Services.prefs.getIntPref( - PREF_APP_UPDATE_CANCELATIONS_OSX, - 0 - ); - osxCancelations++; - Services.prefs.setIntPref( - PREF_APP_UPDATE_CANCELATIONS_OSX, - osxCancelations - ); - let maxCancels = Services.prefs.getIntPref( - PREF_APP_UPDATE_CANCELATIONS_OSX_MAX, - DEFAULT_CANCELATIONS_OSX_MAX - ); - // Prevent the preference from setting a value greater than 5. - maxCancels = Math.min(maxCancels, 5); - if (osxCancelations >= maxCancels) { - cleanupReadyUpdate(); + if (AppConstants.TOR_BROWSER_UPDATE) { + cleanupActiveUpdates(); } else { - writeStatusFile( - getReadyUpdateDir(), - (update.state = STATE_PENDING_ELEVATE) + let osxCancelations = Services.prefs.getIntPref( + PREF_APP_UPDATE_CANCELATIONS_OSX, + 0 + ); + osxCancelations++; + Services.prefs.setIntPref( + PREF_APP_UPDATE_CANCELATIONS_OSX, + osxCancelations ); + let maxCancels = Services.prefs.getIntPref( + PREF_APP_UPDATE_CANCELATIONS_OSX_MAX, + DEFAULT_CANCELATIONS_OSX_MAX + ); + // Prevent the preference from setting a value greater than 5. + maxCancels = Math.min(maxCancels, 5); + if (osxCancelations >= maxCancels) { + cleanupReadyUpdate(); + } else { + writeStatusFile( + getReadyUpdateDir(), + (update.state = STATE_PENDING_ELEVATE) + ); + } } update.statusText = gUpdateBundle.GetStringFromName("elevationFailure"); } else { @@ -1809,6 +1834,15 @@ function updateIsAtLeastAsOldAs(update, version, buildID) { ); }
+/** + * This returns the current version of the browser to use to check updates. + */ +function getCompatVersion() { + return AppConstants.TOR_BROWSER_VERSION + ? AppConstants.TOR_BROWSER_VERSION + : Services.appinfo.version; +} + /** * This returns true if the passed update is the same version or older than * currently installed Firefox version. @@ -1816,7 +1850,7 @@ function updateIsAtLeastAsOldAs(update, version, buildID) { function updateIsAtLeastAsOldAsCurrentVersion(update) { return updateIsAtLeastAsOldAs( update, - Services.appinfo.version, + getCompatVersion(), Services.appinfo.appBuildID ); } @@ -2105,7 +2139,31 @@ 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"].includes( + minInstructionSet + ) + ) { + try { + this.unsupported = !Services.sysinfo.getProperty( + "has" + minInstructionSet + ); + } catch (e) {} + } + } + + if (!this._patches.length && !this.unsupported) { throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE); }
@@ -2143,9 +2201,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": @@ -2170,7 +2226,7 @@ function Update(update) { }
if (!this.previousAppVersion) { - this.previousAppVersion = Services.appinfo.version; + this.previousAppVersion = getCompatVersion(); }
if (!this.elevationFailure) { @@ -2529,6 +2585,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. @@ -2985,6 +3044,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: async function AUS_onCheckComplete(request, updates) { await this._selectAndInstallUpdate(updates); }, @@ -3004,6 +3092,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 @@ -3833,7 +3926,7 @@ UpdateService.prototype = { "UpdateService:downloadUpdate - canceling download of update since " + "it is for an earlier or same application version and build ID.\n" + "current application version: " + - Services.appinfo.version + + getCompatVersion() + "\n" + "update application version : " + update.appVersion + @@ -4507,7 +4600,7 @@ Checker.prototype = { _callback: null,
_getCanMigrate: function UC__getCanMigrate() { - if (AppConstants.platform != "win") { + if (AppConstants.platform != "win" || AppConstants.TOR_BROWSER_VERSION) { return false; }
@@ -5769,6 +5862,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; @@ -5880,7 +5974,20 @@ 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 @@ -5994,7 +6101,11 @@ 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); }); @@ -6179,6 +6290,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/toolkit/mozapps/update/UpdateServiceStub.jsm b/toolkit/mozapps/update/UpdateServiceStub.jsm index fdccbc19db2c..69d89f466862 100644 --- a/toolkit/mozapps/update/UpdateServiceStub.jsm +++ b/toolkit/mozapps/update/UpdateServiceStub.jsm @@ -80,8 +80,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) ) { Services.prefs.setBoolPref(prefUpdateDirMigrated, true); diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp index b094d9eb75e9..c825d3c1ea8e 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/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm index 7721da8e5976..735c98ee5a90 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 40d7a77a6b62..ac7f82a4f9ad 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 2c13b6e45226..2dc08e19957c 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. * @@ -75,7 +75,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); bool PerformInstallationFromDMG(int argc, char** argv); struct UpdateServerThreadArgs { @@ -490,7 +492,8 @@ static const NS_tchar* get_relative_path(const NS_tchar* fullpath) { * Whether the path is a directory path. 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)); @@ -526,10 +529,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; @@ -569,7 +574,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 @@ -756,11 +761,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) { @@ -847,12 +850,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); } @@ -909,7 +919,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", @@ -917,7 +927,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)); @@ -926,7 +941,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 " @@ -946,7 +966,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 @@ -1045,7 +1065,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; } @@ -1063,8 +1095,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; }
@@ -1079,6 +1121,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 @@ -1095,6 +1139,7 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { "file: " LOG_S, relPath)); } +# endif } #else if (rv) { @@ -1149,7 +1194,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; @@ -1160,6 +1205,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) { @@ -1182,28 +1228,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('/')); @@ -1955,6 +2012,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 @@ -2410,14 +2553,29 @@ static bool IsServiceSpecificErrorCode(int errorCode) { */ 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")); @@ -2426,6 +2584,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); }
@@ -2563,7 +2734,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 }
@@ -2571,8 +2744,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
@@ -2746,11 +2956,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(); }
@@ -2780,7 +2994,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"); @@ -2875,8 +3089,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 @@ -3616,6 +3834,26 @@ int NS_main(int argc, NS_tchar** argv) { if (!useService && !noServiceFallback && (updateLockFileHandle == INVALID_HANDLE_VALUE || forceServiceFallback)) { +# 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'}; @@ -3685,6 +3923,7 @@ int NS_main(int argc, NS_tchar** argv) { gCopyOutputFiles = false; WriteStatusFile(ELEVATION_CANCELED); } +# endif /* TOR_BROWSER_UPDATE */ }
// If we started the elevated updater, and it finished, check the secure @@ -4055,6 +4294,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 @@ -4074,6 +4314,7 @@ int NS_main(int argc, NS_tchar** argv) { "directory: " LOG_S, DELETE_DIR)); } +# endif /* TOR_BROWSER_UPDATE */ } #endif /* XP_WIN */
@@ -4718,6 +4959,10 @@ int DoUpdate() { action = new AddIfNotFile(); } else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists action = new PatchIfFile(); +#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); diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h index f8dc75ee4d08..ce816acd83e2 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 ec570ffab124..da2917c2a99e 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 686d8dd985e6..c5e285c7e746 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -3651,6 +3651,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;
@@ -3736,6 +3741,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()); @@ -5211,8 +5222,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 70c8fd45c061..6808b5a5e9cf 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -48,7 +48,6 @@ # include "commonupdatedir.h" # include "nsWindowsHelpers.h" # include "pathhash.h" -# include "WinUtils.h" # define getcwd(path, size) _getcwd(path, size) # define getpid() GetCurrentProcessId() #elif defined(XP_UNIX) @@ -65,6 +64,16 @@ static LazyLogModule sUpdateLog("updatedriver"); #endif #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
+#ifdef XP_WIN +# define UPDATER_BIN "updater.exe" +# define MAINTENANCE_SVC_NAME L"MozillaMaintenance" +#elif XP_MACOSX +# define UPDATER_APP "updater.app" +# define UPDATER_BIN "org.mozilla.updater" +#else +# define UPDATER_BIN "updater" +#endif + #ifdef XP_MACOSX static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv, bool restart) { @@ -78,7 +87,7 @@ static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv, // result from it, so we can't just dispatch and return, we have to wait // until the dispatched operation actually completes. So we also set up a // monitor to signal us when that happens, and block until then. - Monitor monitor MOZ_UNANNOTATED("nsUpdateDriver SetupMacCommandLine"); + Monitor monitor("nsUpdateDriver SetupMacCommandLine");
nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( "UpdateDriverSetupMacCommandLine", @@ -157,6 +166,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; @@ -218,6 +234,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. @@ -290,9 +334,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. * @@ -439,6 +513,7 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, gfxPlatformMac::WaitForFontRegistration(); }
+# 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). @@ -465,6 +540,7 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } } } +# endif #endif
nsAutoCString applyToDirPath; @@ -475,7 +551,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 @@ -570,6 +651,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 @@ -608,7 +692,14 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } #elif defined(XP_MACOSX) UpdateDriverSetupMacCommandLine(argc, argv, restart); -if (restart && needElevation) { +# 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). +if (restart && !IsRecursivelyWritable(installDirPath.get())) { bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid); free(argv); if (!hasLaunched) { @@ -617,6 +708,7 @@ if (restart && needElevation) { } exit(0); } +# endif
if (isStaged) { // Launch the updater to replace the installation with the staged updated. @@ -689,14 +781,25 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, bool restart, ProcessType* pid) { nsresult rv;
-#ifdef XP_WIN - // If we're in a package, we know any updates that we find are not for us. - if (mozilla::widget::WinUtils::HasPackageIdentity()) { - return NS_OK; +#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
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); @@ -716,6 +819,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: { @@ -779,13 +888,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 c39c1fabd57c..13a5878df8ed 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -1197,6 +1197,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)); @@ -1204,7 +1235,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; @@ -1238,7 +1269,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); @@ -1258,7 +1289,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 4b994f30169c..26eabbf31379 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 db2c5898efdc..603988997405 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 24d68616731a..1adfef8fd96e 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit d2a93a95b633e77a80572f833acfb35747be7cb9 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 | 75 +++++++++++++++++++++- 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, 203 insertions(+), 26 deletions(-)
diff --git a/browser/config/mozconfigs/tor-browser b/browser/config/mozconfigs/tor-browser index 8969a88aeaf9..0c10a3334cc2 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 0bf2cb4bd1d4..ea2b79924914 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 0f25dcc10aa4..d52f4415104d 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 CoreFoundation", "-framework Security", diff --git a/modules/libmar/verify/moz.build b/modules/libmar/verify/moz.build index 17bf7b8387bb..202fbef4e06b 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 29968b3b612e..c74fd5b3dc2b 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 ddef19a3782c..6ebbc1e0d0c6 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -996,6 +996,21 @@ 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. @@ -1941,6 +1956,8 @@ function UpdatePatch(patch) { } break; case "finalURL": + case "hashFunction": + case "hashValue": case "state": case "type": case "URL": @@ -1960,6 +1977,8 @@ UpdatePatch.prototype = { // over writing nsIUpdatePatch attributes. _attrNames: [ "errorCode", + "hashFunction", + "hashValue", "finalURL", "selected", "size", @@ -1973,6 +1992,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); @@ -5157,7 +5178,53 @@ 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 Components.Exception("", 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; },
/** @@ -5738,6 +5805,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 @@ -5756,6 +5826,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 e75fd504866a..2612ed65529d 100644 --- a/toolkit/mozapps/update/UpdateTelemetry.jsm +++ b/toolkit/mozapps/update/UpdateTelemetry.jsm @@ -195,6 +195,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 668564822d24..ce1b40054de8 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 7c58d374bbc2..4455b5093d73 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,14 +40,18 @@ if CONFIG["OS_ARCH"] == "WINNT": "ws2_32", "shell32", "shlwapi", - "crypt32", - "advapi32", "gdi32", "user32", "userenv", "uuid", ]
+ if not link_with_nss: + OS_LIBS += [ + "crypt32", + "advapi32", + ] + USE_LIBS += [ "bspatch", "mar", @@ -51,6 +59,13 @@ USE_LIBS += [ "xz-embedded", ]
+if link_with_nss: + USE_LIBS += [ + "nspr", + "nss", + "signmar", + ] + if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": have_progressui = 1 srcs += [ @@ -65,9 +80,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 2dc08e19957c..a0ddffaf0af9 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -108,9 +108,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" @@ -2856,8 +2858,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); } } } @@ -3117,17 +3124,17 @@ int NS_main(int argc, NS_tchar** argv) { if (!isDMGInstall) { // Skip update-related code path for DMG installs.
-#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), (int)error); _exit(1); } +# endif #endif
// To process an update the updater command line must at a minimum have the diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build index 6475c0296aac..a551edb8461f 100644 --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build @@ -232,6 +232,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 6808b5a5e9cf..4936114f579d 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -367,6 +367,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. * @@ -650,6 +686,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 0b32c19df7417afbac4c5240c20fc0142496b27c 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.js | 4 + browser/base/jar.mn | 5 + browser/components/BrowserContentHandler.jsm | 55 +++++++--- browser/components/BrowserGlue.jsm | 15 +++ browser/components/about/AboutRedirector.cpp | 7 ++ browser/components/about/components.conf | 3 + browser/components/moz.build | 5 +- .../locales/en-US/chrome/browser/aboutTBUpdate.dtd | 8 ++ browser/locales/jar.mn | 3 + 15 files changed, 368 insertions(+), 15 deletions(-)
diff --git a/browser/actors/AboutTBUpdateChild.jsm b/browser/actors/AboutTBUpdateChild.jsm new file mode 100644 index 000000000000..4670da19b3db --- /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 000000000000..56a10394565a --- /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 5a2c1796fb5d..d9c06c795a3d 100644 --- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -95,3 +95,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 000000000000..7c1a34b77f17 --- /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 000000000000..ec070e2cb131 --- /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 000000000000..8489cfef5083 --- /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.js b/browser/base/content/browser.js index c0ba0466fb5f..1fe0763c0928 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -658,6 +658,10 @@ var gInitialPages = [ "about:welcomeback", ];
+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 d2b075a50f39..02ee0b359515 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -34,6 +34,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 714a5fac8bf0..e2b06b4fe8a0 100644 --- a/browser/components/BrowserContentHandler.jsm +++ b/browser/components/BrowserContentHandler.jsm @@ -653,6 +653,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 = ""; @@ -695,6 +712,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( @@ -716,6 +743,20 @@ nsBrowserContentHandler.prototype = { "%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) { @@ -788,20 +829,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 0092d25948e7..fe46dec2c42d 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -794,6 +794,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"], + }; +} + XPCOMUtils.defineLazyGetter( this, "WeaveService", diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 0a495a223e3e..faf809728352 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -133,6 +133,13 @@ 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 | + nsIAboutModule::IS_SECURE_CHROME_UI}, +#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 6095653409cd..a1eb66c9fc95 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -34,6 +34,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 ab1dac40dfab..8033a5985ed0 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -93,11 +93,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 000000000000..2d1e59b40eaf --- /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 167a695a46a8..2d4c50b8e7d1 100644 --- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -21,6 +21,9 @@ @AB_CD@.jar: % locale browser @AB_CD@ %locale/browser/ locale/browser/accounts.properties (%chrome/browser/accounts.properties) +#ifdef TOR_BROWSER_UPDATE + locale/browser/aboutTBUpdate.dtd (%chrome/browser/aboutTBUpdate.dtd) +#endif locale/browser/browser.properties (%chrome/browser/browser.properties) locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties) locale/browser/uiDensity.properties (%chrome/browser/uiDensity.properties)
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 01d3c8d618db89e93a05f75199cf1f564ccc7acd 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 44fd95dcff89..d579cf801e1a 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 90f8e6e82c63..7cbfa77d06e7 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 1d94f88ad73b..0103a171de88 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 474706c4b73c..fcee3944e9b7 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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 9edcad9dce7841eb536f1d7712d7174644e86fce 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 ----------- .../google/_locales/region-by/messages.json | 20 ---------- .../google/_locales/region-kz/messages.json | 20 ---------- .../google/_locales/region-ru/messages.json | 20 ---------- .../google/_locales/region-tr/messages.json | 20 ---------- .../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 | 44 ++++++++------------- .../mozapps/extensions/internal/XPIProvider.jsm | 6 +++ 118 files changed, 244 insertions(+), 2016 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 000000000000..92d832ded172 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 000000000000..e7d15ee50247 --- /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 000000000000..f4869e87e008 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 000000000000..0f16b9049cf2 --- /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 000000000000..13c325f6585f 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 000000000000..49f3c116106b --- /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 560000b03e2c..000000000000 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 000000000000..c853b95b89ef 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 782b860a679f..4a3b0ad20fe9 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 1b9d05307d64..000000000000 --- 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 b470cd844331..000000000000 --- 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 a6423089d9f9..000000000000 --- 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 70939ee00074..000000000000 --- 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 aeca0ef128b3..000000000000 --- 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/_locales/region-by/messages.json b/browser/components/search/extensions/google/_locales/region-by/messages.json deleted file mode 100644 index 60e5ed5eda07..000000000000 --- a/browser/components/search/extensions/google/_locales/region-by/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.by/search" - }, - "searchForm": { - "message": "https://www.google.by/search?q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.by/complete/search?client=firefox&q=%7BsearchTerms%7D" - }, - "searchUrlGetParams": { - "message": "q={searchTerms}" - } -} diff --git a/browser/components/search/extensions/google/_locales/region-kz/messages.json b/browser/components/search/extensions/google/_locales/region-kz/messages.json deleted file mode 100644 index 8e64096bc114..000000000000 --- a/browser/components/search/extensions/google/_locales/region-kz/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.kz/search" - }, - "searchForm": { - "message": "https://www.google.kz/search?q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.kz/complete/search?client=firefox&q=%7BsearchTerms%7D" - }, - "searchUrlGetParams": { - "message": "q={searchTerms}" - } -} diff --git a/browser/components/search/extensions/google/_locales/region-ru/messages.json b/browser/components/search/extensions/google/_locales/region-ru/messages.json deleted file mode 100644 index 8a78bb4e7f87..000000000000 --- a/browser/components/search/extensions/google/_locales/region-ru/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.ru/search" - }, - "searchForm": { - "message": "https://www.google.ru/search?q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.ru/complete/search?client=firefox&q=%7BsearchTerms%7D" - }, - "searchUrlGetParams": { - "message": "q={searchTerms}" - } -} diff --git a/browser/components/search/extensions/google/_locales/region-tr/messages.json b/browser/components/search/extensions/google/_locales/region-tr/messages.json deleted file mode 100644 index 8e373a4833b9..000000000000 --- a/browser/components/search/extensions/google/_locales/region-tr/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extensionName": { - "message": "Google" - }, - "extensionDescription": { - "message": "Google Search" - }, - "searchUrl": { - "message": "https://www.google.com.tr/search" - }, - "searchForm": { - "message": "https://www.google.com.tr/search?q=%7BsearchTerms%7D" - }, - "suggestUrl": { - "message": "https://www.google.com.tr/complete/search?client=firefox&q=%7BsearchTerm..." - }, - "searchUrlGetParams": { - "message": "q={searchTerms}" - } -} diff --git a/browser/components/search/extensions/google/manifest.json b/browser/components/search/extensions/google/manifest.json index 6cd4d2a42358..e84a269e4ea1 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 000000000000..44b94a986fd2 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 000000000000..18041d3a2f83 --- /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 000000000000..e5aaff437912 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 000000000000..59714e0e1045 --- /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 e4ee66bc780d..000000000000 --- 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 ec016ac7337e..000000000000 --- 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 8cf9de8ac9b3..000000000000 --- 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 e8cce665c96e..000000000000 --- 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 de90b2a2055e..000000000000 --- 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 a127ba07f29b..000000000000 --- 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 f551a717e6d3..000000000000 --- 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 aecfecf2fb19..000000000000 --- 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 6aa763451e67..000000000000 --- 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 896a85d66b87..000000000000 --- 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 fe9887ed1938..000000000000 --- 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 33869ce8e752..000000000000 --- 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 746150e3d8e8..000000000000 --- 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 151ec1a71ba5..000000000000 --- 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 cfed7c73be34..000000000000 --- 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 12f7eb22d711..000000000000 --- 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 801d5a5183cc..000000000000 --- 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 0e6bbe8905ca..000000000000 --- 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 ffca44b5f7fb..000000000000 --- 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 95b48f3d9ca7..000000000000 --- 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 0de3c9a8071a..000000000000 --- 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 10aa88dd11ba..000000000000 --- 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 09ec1f757657..000000000000 --- 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 91363fbb392b..000000000000 --- 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 1bd7027dec54..000000000000 --- 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 9fdc964a1e0b..000000000000 --- 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 17a9cbe22c42..000000000000 --- 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 33dcbe9dc502..000000000000 --- 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 f350162fbbaf..000000000000 --- 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 994ea723c6da..000000000000 --- 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 f16f16fb4a02..000000000000 --- 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 88880bffc3d9..000000000000 --- 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 5efc5ed74a95..000000000000 --- 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 3d2f68826fc5..000000000000 --- 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 1f8471e980f0..000000000000 --- 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 f3b7d14eafa0..000000000000 --- 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 18a6177efcca..000000000000 --- 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 d4e62836e6e9..000000000000 --- 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 68300c48a6f3..000000000000 --- 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 56c2ae2c641b..000000000000 --- 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 6d997ae8fc81..000000000000 --- 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 1d35e71b956d..000000000000 --- 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 f722d88187de..000000000000 --- 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 2ca645740f87..000000000000 --- 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 7215e68768f0..000000000000 --- 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 c460a093e5e4..000000000000 --- 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 3cf743b616fe..000000000000 --- 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 0844cca0d7e1..000000000000 --- 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 0f0a0880e188..000000000000 --- 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 379ef20085a3..000000000000 --- 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 54296cac62bd..000000000000 --- 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 cb90db5e4099..000000000000 --- 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 712746ec6316..000000000000 --- 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 c061bcc5224c..000000000000 --- 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 0e02810ef3bf..000000000000 --- 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 f73814b8574f..000000000000 --- 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 de7e06e1ac4a..000000000000 --- 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 bd46dd83700c..000000000000 --- 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 c817e82c7821..000000000000 --- 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 62342d1b90ae..000000000000 --- 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 eb22344341e4..000000000000 --- 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 c2a810c2ae30..000000000000 --- 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 3cadc3d68f07..000000000000 --- 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 dff38c2146fd..000000000000 --- 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 315aa0d9cbe1..000000000000 --- 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 4beaa97acc88..000000000000 --- 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 8258d5e43451..000000000000 --- 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 48865fd547e4..000000000000 --- 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 569467691d7c..000000000000 --- 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 0406ae728d71..000000000000 --- 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 5c2f75f8b031..000000000000 --- 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 7385a2203474..000000000000 --- 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 68361d8ab294..000000000000 --- 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 50ebc0a197a1..000000000000 --- 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 1edc3db80d98..000000000000 --- 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 54397603b028..000000000000 --- 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 c474be12a76f..000000000000 --- 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 3d6aeb07ca2c..000000000000 --- 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 d55b03131f97..000000000000 --- 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 878b28ab68b2..000000000000 --- 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 2749b86304bf..000000000000 --- 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 dcc87e0c853c..000000000000 --- 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 89a8f2a89bca..000000000000 --- 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 c0844e4feb14..000000000000 --- 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 7efde3b1d0f4..000000000000 --- 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 29047565a243..000000000000 --- 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 a0e8d880ea26..000000000000 --- 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 cd95d3aa9e1c..0af3b2a1c211 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 000000000000..9bd1d9f7c008 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 000000000000..e1f04a373c2e --- /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 000000000000..977887dbbb84 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 000000000000..6fbf8745bac2 --- /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 000000000000..3bb90f51a305 --- /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 9ac351a248b7..5094ce8aec9e 100644 --- a/toolkit/components/search/SearchService.jsm +++ b/toolkit/components/search/SearchService.jsm @@ -12,14 +12,12 @@ const { PromiseUtils } = ChromeUtils.import( );
XPCOMUtils.defineLazyModuleGetters(this, { - AppConstants: "resource://gre/modules/AppConstants.jsm", AddonManager: "resource://gre/modules/AddonManager.jsm", IgnoreLists: "resource://gre/modules/IgnoreLists.jsm", OpenSearchEngine: "resource://gre/modules/OpenSearchEngine.jsm", 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", @@ -239,11 +237,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();
@@ -1164,26 +1157,21 @@ SearchService.prototype = { },
async _fetchEngineSelectorEngines() { - let searchEngineSelectorProperties = { - locale: Services.locale.appLocaleAsBCP47, - region: Region.home || "default", - channel: AppConstants.MOZ_APP_VERSION_DISPLAY.endsWith("esr") - ? "esr" - : AppConstants.MOZ_UPDATE_CHANNEL, - experiment: NimbusFeatures.search.getVariable("experiment") ?? "", - distroID: SearchUtils.distroID ?? "", - }; - - for (let [key, value] of Object.entries(searchEngineSelectorProperties)) { - this._settings.setAttribute(key, value); - } - - let { - engines, - privateDefault, - } = await this._engineSelector.fetchEngineConfiguration( - searchEngineSelectorProperties - ); + 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) { @@ -1192,7 +1180,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 7d8bc93933bf..ed2288a8939d 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -972,6 +972,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit e595af6f0f647c16ffd6c6e35b1a94f29b8b601a 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 | 61 +++++++++++++++++----- browser/base/content/pageinfo/security.js | 49 +++++++++++++++-- .../shared/identity-block/identity-block.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/certverifier/CertVerifier.cpp | 22 ++++++-- security/manager/ssl/SSLServerCertVerification.cpp | 14 ++++- security/manager/ssl/nsNSSIOLayer.cpp | 13 +++-- security/manager/ssl/nsSecureBrowserUI.cpp | 12 +++++ security/nss/lib/mozpkix/include/pkix/Result.h | 2 + security/nss/lib/mozpkix/include/pkix/pkixnss.h | 1 + 16 files changed, 217 insertions(+), 33 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 02fdb494d290..64a806719a77 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -139,6 +139,12 @@ var gIdentityHandler = { ); },
+ get _uriIsOnionHost() { + return this._uriHasHost + ? this._uri.host.toLowerCase().endsWith(".onion") + : false; + }, + get _isAboutNetErrorPage() { let { documentURI } = gBrowser.selectedBrowser; return documentURI?.scheme == "about" && documentURI.filePath == "neterror"; @@ -737,9 +743,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"; },
/** @@ -747,12 +753,18 @@ 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 ]; - - return !issuerCert.isBuiltInRoot; + if (issuerCert) { + return !issuerCert.isBuiltInRoot; + } + return false; },
/** @@ -789,11 +801,17 @@ 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. @@ -804,17 +822,27 @@ 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" + 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._isCertErrorPage) { @@ -830,8 +858,10 @@ 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 = @@ -855,7 +885,10 @@ 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 1222c8b0ec35..a5d354208d79 100644 --- a/browser/base/content/pageinfo/security.js +++ b/browser/base/content/pageinfo/security.js @@ -22,6 +22,11 @@ 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 +65,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 +79,7 @@ var security = { isBroken, isMixed, isEV, + isOnion, cert: null, certificateTransparency: null, }; @@ -107,6 +118,7 @@ var security = { isBroken, isMixed, isEV, + isOnion, cert, certChain: certChainArray, certificateTransparency: undefined, @@ -348,13 +360,31 @@ 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 { + } else if (!info.isOnion) { hdr = pkiBundle.getString("pageInfo_NoEncryption"); if (windowInfo.hostName != null) { msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [ @@ -364,6 +394,15 @@ async function securityOnLoad(uri, windowInfo) { msg1 = pkiBundle.getString("pageInfo_Privacy_None4"); } msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); + } else { + 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"); } setText("security-technical-shortform", hdr); setText("security-technical-longform1", msg1); diff --git a/browser/themes/shared/identity-block/identity-block.css b/browser/themes/shared/identity-block/identity-block.css index 63c07d21a453..7314a6570fb8 100644 --- a/browser/themes/shared/identity-block/identity-block.css +++ b/browser/themes/shared/identity-block/identity-block.css @@ -193,6 +193,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 0562245205a3..0dced75b718a 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -9451,6 +9451,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 739e8ca23c85..12d1c3b998b8 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -3014,6 +3014,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 03b90e6098d8..6697ae5ee2ab 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -1882,7 +1882,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 3413442f1137..7d6a028d676f 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" @@ -133,7 +134,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 f209705183cb..f9ce500f18e2 100644 --- a/dom/ipc/WindowGlobalChild.cpp +++ b/dom/ipc/WindowGlobalChild.cpp @@ -42,6 +42,8 @@ #include "nsIHttpChannelInternal.h" #include "nsIURIMutator.h"
+#include "mozilla/dom/nsMixedContentBlocker.h" + using namespace mozilla::ipc; using namespace mozilla::dom::ipc;
@@ -228,7 +230,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 e4352e3cb43b..2a7d878a2fcf 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -666,8 +666,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) { @@ -682,6 +682,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 " @@ -708,7 +719,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 d121ce850f7f..e054bc6bd709 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/certverifier/CertVerifier.cpp b/security/certverifier/CertVerifier.cpp index b6ee9f699ea8..2ad18700bd44 100644 --- a/security/certverifier/CertVerifier.cpp +++ b/security/certverifier/CertVerifier.cpp @@ -907,6 +907,9 @@ Result CertVerifier::VerifySSLServerCert( if (rv != Success) { return rv; } + + bool errOnionWithSelfSignedCert = false; + bool isBuiltChainRootBuiltInRootLocal; rv = VerifyCert(peerCertBytes, certificateUsageSSLServer, time, pinarg, PromiseFlatCString(hostname).get(), builtChain, flags, @@ -926,9 +929,16 @@ Result CertVerifier::VerifySSLServerCert( CertIsSelfSigned(peerBackCert, pinarg)) { // In this case we didn't find any issuer for the certificate and the // certificate is self-signed. - return Result::ERROR_SELF_SIGNED_CERT; + if (StringEndsWith(hostname, ".onion"_ns)) { + // Self signed cert over onion is deemed secure, the hidden service provides authentication. + // We defer returning this error and keep processing to determine if there are other legitimate + // certificate errors (such as expired, wrong domain) that we would like to surface to the user + errOnionWithSelfSignedCert = true; + } else { + return Result::ERROR_SELF_SIGNED_CERT; + } } - if (rv == Result::ERROR_UNKNOWN_ISSUER) { + if (rv == Result::ERROR_UNKNOWN_ISSUER && !errOnionWithSelfSignedCert) { // In this case we didn't get any valid path for the cert. Let's see if // the issuer is the same as the issuer for our canary probe. If yes, this // connection is connecting via a misconfigured proxy. @@ -952,7 +962,9 @@ Result CertVerifier::VerifySSLServerCert( return Result::ERROR_MITM_DETECTED; } } - return rv; + if (!errOnionWithSelfSignedCert) { + return rv; + } }
if (dcInfo) { @@ -1002,7 +1014,9 @@ Result CertVerifier::VerifySSLServerCert( if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = isBuiltChainRootBuiltInRootLocal; } - + if (errOnionWithSelfSignedCert) { + return Result::ERROR_ONION_WITH_SELF_SIGNED_CERT; + } return Success; }
diff --git a/security/manager/ssl/SSLServerCertVerification.cpp b/security/manager/ssl/SSLServerCertVerification.cpp index 52628799176c..2df79a3bc11a 100644 --- a/security/manager/ssl/SSLServerCertVerification.cpp +++ b/security/manager/ssl/SSLServerCertVerification.cpp @@ -309,6 +309,7 @@ SECStatus DetermineCertOverrideErrors(const nsCOMPtr<nsIX509Cert>& cert, case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED: case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: + case mozilla::pkix::MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT: case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA: { collectedErrors = nsICertOverrideService::ERROR_UNTRUSTED; errorCodeTrust = defaultErrorCodeToReport; @@ -830,6 +831,17 @@ PRErrorCode AuthCertificateParseResults( gPIPNSSLog, LogLevel::Debug, ("[0x%" PRIx64 "] Certificate error was not overridden\n", aPtrForLog));
+ // If Onion with self signed cert we want to prioritize any other error + if (errorCodeTrust == MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { + if (errorCodeMismatch) { + return errorCodeMismatch; + } else if (errorCodeTime) { + return errorCodeTime; + } else { + return MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT; + } + } + // pick the error code to report by priority return errorCodeTrust ? errorCodeTrust : errorCodeMismatch ? errorCodeMismatch @@ -1257,7 +1269,7 @@ SSLServerCertVerificationResult::Run() { // Certificate validation failed; store the peer certificate chain on // infoObject so it can be used for error reporting. mInfoObject->SetFailedCertChain(std::move(mPeerCertChain)); - if (mCollectedErrors != 0) { + if (mCollectedErrors != 0 && mFinalError != MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { mInfoObject->SetStatusErrorBits(cert, mCollectedErrors); } } diff --git a/security/manager/ssl/nsNSSIOLayer.cpp b/security/manager/ssl/nsNSSIOLayer.cpp index 2c36893324ff..d79237478cdd 100644 --- a/security/manager/ssl/nsNSSIOLayer.cpp +++ b/security/manager/ssl/nsNSSIOLayer.cpp @@ -420,7 +420,11 @@ void nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode) { "Invalid state transition to cert_verification_finished");
if (mFd) { - SECStatus rv = SSL_AuthCertificateComplete(mFd, errorCode); + PRErrorCode passCode = errorCode; + if (errorCode == MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { + passCode = 0; + } + SECStatus rv = SSL_AuthCertificateComplete(mFd, passCode); // Only replace errorCode if there was originally no error if (rv != SECSuccess && errorCode == 0) { errorCode = PR_GetError(); @@ -431,12 +435,15 @@ void nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode) { } }
- if (errorCode) { + if (errorCode && + errorCode != MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { mFailedVerification = true; SetCanceled(errorCode); }
- if (mPlaintextBytesRead && !errorCode) { + if (mPlaintextBytesRead && + (!errorCode || + errorCode == MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT)) { Telemetry::Accumulate(Telemetry::SSL_BYTES_BEFORE_CERT_CALLBACK, AssertedCast<uint32_t>(mPlaintextBytesRead)); } diff --git a/security/manager/ssl/nsSecureBrowserUI.cpp b/security/manager/ssl/nsSecureBrowserUI.cpp index b4de1a331ffc..f1ce39582854 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 diff --git a/security/nss/lib/mozpkix/include/pkix/Result.h b/security/nss/lib/mozpkix/include/pkix/Result.h index 29461dc1a510..b2ad3a383ceb 100644 --- a/security/nss/lib/mozpkix/include/pkix/Result.h +++ b/security/nss/lib/mozpkix/include/pkix/Result.h @@ -188,6 +188,8 @@ static const unsigned int FATAL_ERROR_FLAG = 0x800; SEC_ERROR_LIBRARY_FAILURE) \ MOZILLA_PKIX_MAP(FATAL_ERROR_NO_MEMORY, FATAL_ERROR_FLAG | 4, \ SEC_ERROR_NO_MEMORY) \ + MOZILLA_PKIX_MAP(ERROR_ONION_WITH_SELF_SIGNED_CERT, 155, \ + MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) \ /* nothing here */
enum class Result { diff --git a/security/nss/lib/mozpkix/include/pkix/pkixnss.h b/security/nss/lib/mozpkix/include/pkix/pkixnss.h index c1050c6497a4..75a7c4e9c0fb 100644 --- a/security/nss/lib/mozpkix/include/pkix/pkixnss.h +++ b/security/nss/lib/mozpkix/include/pkix/pkixnss.h @@ -94,6 +94,7 @@ enum ErrorCode { MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = ERROR_BASE + 13, MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = ERROR_BASE + 14, MOZILLA_PKIX_ERROR_MITM_DETECTED = ERROR_BASE + 15, + MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT = ERROR_BASE + 100, END_OF_LIST };
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 491f7f18a59ebde927f35619817efeca26d9ba3c 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/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 | 68 ++++ .../components/onionservices/content/authPrompt.js | 359 +++++++++++++++++++++ .../components/onionservices/content/authUtil.jsm | 46 +++ .../onionservices/content/netError/browser.svg | 3 + .../onionservices/content/netError/network.svg | 3 + .../content/netError/onionNetError.css | 70 ++++ .../content/netError/onionNetError.js | 241 ++++++++++++++ .../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/components/sessionstore/SessionStore.jsm | 5 + browser/themes/shared/notification-icons.css | 2 + 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, 1495 insertions(+), 2 deletions(-)
diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm index cea0372ce41d..705884878456 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", @@ -32,6 +34,7 @@ class NetErrorChild extends RemotePageChild { "RPMAddToHistogram", "RPMRecordTelemetryEvent", "RPMGetHttpResponseHeader", + "RPMGetTorStrings", ]; this.exportFunctions(exportableFunctions); } @@ -110,4 +113,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 1fe0763c0928..70d4580750dc 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -234,6 +234,11 @@ XPCOMUtils.defineLazyScriptGetter( ["NewIdentityButton"], "chrome://browser/content/newidentity.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionAuthPrompt"], + "chrome://browser/content/onionservices/authPrompt.js" +); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", @@ -1795,6 +1800,9 @@ var gBrowserInit = { // Init the NewIdentityButton NewIdentityButton.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 @@ -2537,6 +2545,8 @@ var gBrowserInit = {
NewIdentityButton.uninit();
+ OnionAuthPrompt.uninit(); + TorBootstrapUrlbar.uninit();
gAccessibilityServiceIndicator.uninit(); diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 32d971c93d6d..6a9103f4e16e 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -38,6 +38,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"?>
<!DOCTYPE window [ #include browser-doctype.inc diff --git a/browser/base/content/certerror/aboutNetError.js b/browser/base/content/certerror/aboutNetError.js index 1714b1b8a4be..1f63d5aa7012 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"; @@ -291,7 +292,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) { @@ -418,6 +422,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 0f7deb473df2..ea9b21a8f23e 100644 --- a/browser/base/content/certerror/aboutNetError.xhtml +++ b/browser/base/content/certerror/aboutNetError.xhtml @@ -203,5 +203,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 029a63bea57c..79a2750d96ce 100644 --- a/browser/base/content/main-popupset.inc.xhtml +++ b/browser/base/content/main-popupset.inc.xhtml @@ -536,6 +536,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 81479615dc72..1cd54285c9a9 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -259,6 +259,7 @@ data-l10n-id="urlbar-indexed-db-notification-anchor"/> <image id="password-notification-icon" class="notification-anchor-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/components/moz.build b/browser/components/moz.build index 8033a5985ed0..1adecb68bc8f 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -40,6 +40,7 @@ DIRS += [ "migration", "newidentity", "newtab", + "onionservices", "originattributes", "places", "pocket", diff --git a/browser/components/onionservices/content/authNotificationIcon.inc.xhtml b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml new file mode 100644 index 000000000000..91274d612739 --- /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 000000000000..bd0ec3aa0b00 --- /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 000000000000..b3fb79b26ddc --- /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 000000000000..f69c9dde66a2 --- /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 000000000000..5afd7d0dd474 --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.js @@ -0,0 +1,68 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +/* globals gSubDialog */ + +/* + 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 000000000000..14fb334a6206 --- /dev/null +++ b/browser/components/onionservices/content/authPrompt.js @@ -0,0 +1,359 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +/* globals gBrowser, PopupNotifications, Services, XPCOMUtils */ + +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.firstChild.remove(); + } + + 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 } = ChromeUtils.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 000000000000..82a835359b3e --- /dev/null +++ b/browser/components/onionservices/content/authUtil.jsm @@ -0,0 +1,46 @@ +// 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(aMessageManager, aDocShell) { + aMessageManager.addMessageListener(this.message.authPromptCanceled, { + receiveMessage(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. + const failedURI = Services.io.newURI(aMessage.data.failedURI); + const reasonForPrompt = aMessage.data.reasonForPrompt; + const 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 000000000000..1359679f7171 --- /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 000000000000..68610e30bfca --- /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 000000000000..f43817e9a569 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionNetError.css @@ -0,0 +1,70 @@ +/* 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%; +} + +.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 000000000000..aee0ba776619 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionNetError.js @@ -0,0 +1,241 @@ +// 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 000000000000..c1b2d7382dc9 --- /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 000000000000..e2621ec8266d --- /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 000000000000..f102e4e89bca --- /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) { + 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 000000000000..3db9bb05ea82 --- /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 000000000000..9d6ce88d1841 --- /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 000000000000..2661ad7cb9f3 --- /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 cb8716f48a50..729470b683af 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -15,6 +15,7 @@ <?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"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css"?>
<!DOCTYPE html [ <!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd"> diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index 8b2a1c99390d..37f77e3b70da 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -526,6 +526,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 b1413b208521..d584fb5e4624 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -48,6 +48,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, @@ -528,6 +534,7 @@ var gPrivacyPane = { this.trackingProtectionReadPrefs(); this.networkCookieBehaviorReadPrefs(); this._initTrackingProtectionExtensionControl(); + OnionServicesAuthPreferences.init(); this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true); diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 33724bdc1ccb..5f9f6aab2e54 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -239,6 +239,10 @@ const { TorProtocolService } = ChromeUtils.import( "resource:///modules/TorProtocolService.jsm" );
+const { OnionAuthUtil } = ChromeUtils.import( + "chrome://browser/content/onionservices/authUtil.jsm" +); + XPCOMUtils.defineLazyServiceGetters(this, { gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"], }); @@ -1515,6 +1519,7 @@ var SessionStoreInternal = { let listenWhenClosed = CLOSED_MESSAGES.has(msg); mm.addMessageListener(msg, this, listenWhenClosed); }); + OnionAuthUtil.addCancelMessageListener(mm, aWindow.docShell);
// Load the frame script after registering listeners. if (!Services.appinfo.sessionHistoryInParent) { diff --git a/browser/themes/shared/notification-icons.css b/browser/themes/shared/notification-icons.css index a24538ebd5dd..3422aa2f6dff 100644 --- a/browser/themes/shared/notification-icons.css +++ b/browser/themes/shared/notification-icons.css @@ -126,6 +126,8 @@ list-style-image: url(chrome://browser/skin/notification-icons/persistent-storage.svg); }
+.popup-notification-icon[popupid="tor-clientauth"], +.tor-clientauth-icon, #password-notification-icon { list-style-image: url(chrome://browser/skin/login.svg); } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index f886070364b6..95c68c34fd7f 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -3785,6 +3785,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 @@ -3866,10 +3867,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"); + } }
nsresult delegateErrorCode = aError; @@ -3956,6 +3991,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); } @@ -6351,6 +6400,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; @@ -8122,6 +8172,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 498f5e3ff98d..1293ecfd054f 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -3920,6 +3920,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 755168b6dbd0..264ac1948e70 100644 --- a/dom/ipc/BrowserParent.h +++ b/dom/ipc/BrowserParent.h @@ -745,6 +745,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 9447427f3980..5fa2f666756a 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -598,6 +598,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 UpdateSHistory(); diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg index 1995b1445da1..27202b657805 100644 --- a/js/xpconnect/src/xpc.msg +++ b/js/xpconnect/src/xpc.msg @@ -246,6 +246,16 @@ 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 2e3241a50a91..4371def2a08c 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 119a3cbf4c51..f9fc29552ace 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 de7fc8537c55..9242a2975681 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. * If null, the notification will have a default "OK" action button * that can be used to dismiss the popup and secondaryActions will be ignored. * @param secondaryActions @@ -1908,6 +1910,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 6b1da814765f..ec48d9276bde 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -95,6 +95,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 881f8386b3a1..fd86b1ea487c 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 @@ -40,5 +40,6 @@ module.exports = { RPMGetHttpResponseHeader: false, RPMTryPingSecureWWWLink: false, RPMOpenSecureWWWLink: false, + RPMGetTorStrings: false, }, }; diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py index b1f9384c3e23..5ebf1a8f678a 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 @@ -1212,6 +1213,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 2117e1429c87c2c1ce4a3c68b2a7c79fb1a1c716 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 | 169 +++++++++++++++++++++ .../content/onionlocation-urlbar.inc.xhtml | 10 ++ .../onionservices/content/onionlocation.css | 46 ++++++ .../onionservices/content/onionlocation.svg | 3 + .../content/onionlocationPreferences.inc.xhtml | 11 ++ .../content/onionlocationPreferences.js | 34 +++++ browser/components/onionservices/jar.mn | 3 + browser/components/onionservices/moz.build | 5 + browser/components/preferences/privacy.inc.xhtml | 2 + browser/components/preferences/privacy.js | 17 +++ browser/themes/shared/browser-shared.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 + 20 files changed, 426 insertions(+), 1 deletion(-)
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 70d4580750dc..8ed725e04b4b 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", @@ -5516,6 +5517,7 @@ var XULBrowserWindow = { CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser); + OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);
if (!gMultiProcessBrowser) { // Bug 1108553 - Cannot rotate images with e10s @@ -6035,6 +6037,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 1cd54285c9a9..8e199e658a41 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -352,6 +352,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" role="button" diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index fe46dec2c42d..5b60b15029c0 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -582,6 +582,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 000000000000..23e1823f5a09 --- /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 000000000000..4ac4a5d0775e --- /dev/null +++ b/browser/components/onionservices/OnionLocationParent.jsm @@ -0,0 +1,169 @@ +// 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.learnMoreURLNotification; + +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-urlbar.inc.xhtml b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml new file mode 100644 index 000000000000..b612a4236f3c --- /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.css b/browser/components/onionservices/content/onionlocation.css new file mode 100644 index 000000000000..b90b2b9b8d46 --- /dev/null +++ b/browser/components/onionservices/content/onionlocation.css @@ -0,0 +1,46 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +#onion-location-box { + 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 { + margin: 0; + padding-block: 0; + padding-inline: 0.5em; + color: white; + font-weight: normal; +} + +.onionlocation-notification-icon { + display: none; +} diff --git a/browser/components/onionservices/content/onionlocation.svg b/browser/components/onionservices/content/onionlocation.svg new file mode 100644 index 000000000000..37f40ac1812f --- /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 000000000000..c285f403f99b --- /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 000000000000..005e37d4a991 --- /dev/null +++ b/browser/components/onionservices/content/onionlocationPreferences.js @@ -0,0 +1,34 @@ +// 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; + if (TorStrings.onionLocation.learnMoreURL.startsWith("about:")) { + learnMore.setAttribute("useoriginprincipal", "true"); + } + 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 9d6ce88d1841..e778abd206b3 100644 --- a/browser/components/onionservices/jar.mn +++ b/browser/components/onionservices/jar.mn @@ -7,3 +7,6 @@ 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) + skin/classic/browser/onionlocation.css (content/onionlocation.css) diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 2661ad7cb9f3..27f9d2da4a9e 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -1 +1,6 @@ JAR_MANIFESTS += ["jar.mn"] + +EXTRA_JS_MODULES += [ + "OnionLocationChild.jsm", + "OnionLocationParent.jsm", +] diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index 37f77e3b70da..d322527a9853 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 d584fb5e4624..d6a77393b155 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -61,6 +61,12 @@ XPCOMUtils.defineLazyScriptGetter( "chrome://browser/content/securitylevel/securityLevel.js" );
+XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionLocationPreferences"], + "chrome://browser/content/onionservices/onionlocationPreferences.js" +); + XPCOMUtils.defineLazyPreferenceGetter( this, "OS_AUTH_ENABLED", @@ -138,6 +144,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" },
@@ -339,6 +348,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. */ @@ -536,6 +552,7 @@ var gPrivacyPane = { this._initTrackingProtectionExtensionControl(); OnionServicesAuthPreferences.init(); this._initSecurityLevel(); + this._initOnionLocation();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/themes/shared/browser-shared.css b/browser/themes/shared/browser-shared.css index c8dac0afb49a..e771c46dcbf1 100644 --- a/browser/themes/shared/browser-shared.css +++ b/browser/themes/shared/browser-shared.css @@ -22,6 +22,7 @@ @import url("chrome://browser/skin/customizableui/customizeMode.css"); @import url("chrome://browser/skin/UITour.css"); @import url("chrome://browser/skin/torconnect-urlbar.css"); +@import url("chrome://browser/skin/onionlocation.css");
@namespace html url("http://www.w3.org/1999/xhtml");
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 430238686cd0..f429506b0a8d 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -2925,6 +2925,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 @@ -6828,6 +6829,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"); @@ -6903,6 +6920,21 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { if (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); + } + } else { + mOnionLocationURI = onionURI; + } + } + } }
void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource, @@ -11036,7 +11068,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", "origin-trial", + "x-frame-options", "origin-trial", "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 2d9bbba59bce..91dfe3de8028 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3401,6 +3401,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) { @@ -4413,6 +4414,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 3d089ddbe848..9c5f78784cb1 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -748,3 +748,11 @@ partial interface Document { [ChromeOnly] Wireframe? getWireframe(optional boolean aIncludeNodes = false); }; + +/** + * 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 47944c7d6425..f4c32fd193e7 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -11829,6 +11829,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 740b3f6e187d..503a62d5f17b 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -826,6 +826,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 301ab4aa8fe2e09dc96813a6f811773987b4e722 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/components/BrowserGlue.jsm | 42 ++ browser/components/about/AboutRedirector.cpp | 5 + browser/components/about/components.conf | 1 + browser/components/moz.build | 1 + .../components/onionservices/OnionAliasStore.jsm | 563 +++++++++++++++++++++ browser/components/onionservices/moz.build | 1 + 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 + modules/libpref/init/StaticPrefList.yaml | 5 + netwerk/build/components.conf | 11 + netwerk/build/nsNetCID.h | 10 + netwerk/dns/IOnionAliasService.idl | 34 ++ netwerk/dns/OnionAliasService.cpp | 100 ++++ netwerk/dns/OnionAliasService.h | 40 ++ 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 + 26 files changed, 2098 insertions(+), 6 deletions(-)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 5b60b15029c0..0949c795d8c7 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -59,6 +59,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { Normandy: "resource://normandy/Normandy.jsm", OnboardingMessageProvider: "resource://activity-stream/lib/OnboardingMessageProvider.jsm", + OnionAliasStore: "resource:///modules/OnionAliasStore.jsm", OsEnvironment: "resource://gre/modules/OsEnvironment.jsm", PageActions: "resource:///modules/PageActions.jsm", PageThumbs: "resource://gre/modules/PageThumbs.jsm", @@ -682,6 +683,19 @@ let JSWINDOWACTORS = { enablePreference: "accessibility.blockautorefresh", },
+ Rulesets: { + parent: { + moduleURI: "resource:///modules/RulesetsParent.jsm", + }, + child: { + moduleURI: "resource:///modules/RulesetsChild.jsm", + events: { + DOMWindowCreated: {}, + }, + }, + matches: ["about:rulesets*"], + }, + ScreenshotsComponent: { parent: { moduleURI: "resource:///modules/ScreenshotsUtils.jsm", @@ -2042,6 +2056,7 @@ BrowserGlue.prototype = { () => RFPHelper.uninit(), () => ASRouterNewTabHook.destroy(), () => UpdateListener.reset(), + () => OnionAliasStore.uninit(), ];
for (let task of tasks) { @@ -2669,6 +2684,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: () => { TabUnloader.init(); diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index faf809728352..c3095c4bd3bb 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -88,6 +88,11 @@ static const RedirEntry kRedirMap[] = { {"robots", "chrome://browser/content/aboutRobots.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT}, + {"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 | + nsIAboutModule::IS_SECURE_CHROME_UI}, {"sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml", nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::IS_SECURE_CHROME_UI}, diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index a1eb66c9fc95..d35ad34ec8ba 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -26,6 +26,7 @@ pages = [ 'restartrequired', 'rights', 'robots', + 'rulesets', 'sessionrestore', 'tabcrashed', 'torconnect', diff --git a/browser/components/moz.build b/browser/components/moz.build index 1adecb68bc8f..27cda5d68c04 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -49,6 +49,7 @@ DIRS += [ "prompts", "protocolhandler", "resistfingerprinting", + "rulesets", "screenshots", "search", "securitylevel", diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm new file mode 100644 index 000000000000..bc7f8a1d79cf --- /dev/null +++ b/browser/components/onionservices/OnionAliasStore.jsm @@ -0,0 +1,563 @@ +// 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" +); + +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, +}; + +const kPrefOnionAliasEnabled = "browser.urlbar.onionRewrites.enabled"; + +// 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", () => { + const { ConsoleAPI } = ChromeUtils.import( + "resource://gre/modules/Console.jsm" + ); + let consoleOptions = { + maxLogLevel: gDebuggingEnabled ? "all" : "log", + prefix: "OnionAlias", + }; + return new 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(); + if (this.enabled) { + await this._startUpdates(); + } + Services.prefs.addObserver(kPrefOnionAliasEnabled, this); + } + + uninit() { + this._clear(); + if (this._rulesetTimeout) { + clearTimeout(this._rulesetTimeout); + } + this._rulesetTimeout = null; + Services.prefs.removeObserver(kPrefOnionAliasEnabled, this); + } + + 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 (this.enabled && enabled && !channel.currentTimestamp) { + this.updateChannel(name); + } + } + } + + async updateChannel(name) { + if (!this.enabled) { + throw Error("Onion Aliases are disabled"); + } + 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() { + if (!this.enabled) { + log.debug("Onion Aliases are disabled, not updating rulesets."); + return; + } + 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); + } + + async _startUpdates() { + // This is a "private" function, so we expect the callers to verify wheter + // onion aliases are enabled. + // Callees will also do, so we avoid an additional check here. + 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); + } + } + + _scheduleCheck(dt) { + if (this._rulesetTimeout) { + log.warn("The previous update timeout was not null"); + clearTimeout(this._rulesetTimeout); + } + if (!this.enabled) { + log.warn( + "Ignoring the scheduling of a new check because the Onion Alias feature is currently disabled." + ); + this._rulesetTimeout = null; + return; + } + log.debug(`Scheduling ruleset update in ${dt}`); + this._rulesetTimeout = setTimeout(() => { + this._rulesetTimeout = null; + this._periodicRulesetCheck(); + }, dt); + } + + _notifyChanges() { + Services.obs.notifyObservers( + Array.from(this._channels.values(), ch => ch.toJSON()), + OnionAliasStoreTopics.ChannelsChanged + ); + } + + get enabled() { + return Services.prefs.getBoolPref(kPrefOnionAliasEnabled, true); + } + + observe(aSubject, aTopic, aData) { + if (aTopic === "nsPref:changed") { + if (this.enabled) { + this._startUpdates(); + } else if (this._rulesetTimeout) { + clearTimeout(this._rulesetTimeout); + this._rulesetTimeout = null; + } + } + } +} + +const OnionAliasStore = new _OnionAliasStore(); diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 27f9d2da4a9e..8644548caa15 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -1,6 +1,7 @@ JAR_MANIFESTS += ["jar.mn"]
EXTRA_JS_MODULES += [ + "OnionAliasStore.jsm", "OnionLocationChild.jsm", "OnionLocationParent.jsm", ] diff --git a/browser/components/rulesets/RulesetsChild.jsm b/browser/components/rulesets/RulesetsChild.jsm new file mode 100644 index 000000000000..e30de9c7201b --- /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 000000000000..6a9553644cb1 --- /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 000000000000..60b699fe8a02 --- /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 000000000000..d5b03435b1e7 --- /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 000000000000..4fabdca1b93d --- /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 000000000000..69cd584ac1ed --- /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 000000000000..e0b67442d89c --- /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 000000000000..daec4c302524 --- /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/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index f4c32fd193e7..072acf76f8c4 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1526,6 +1526,11 @@ value: true mirror: always
+- name: browser.urlbar.onionRewrites.enabled + type: RelaxedAtomicBool + value: true + mirror: always + - name: browser.viewport.desktopWidth type: RelaxedAtomicInt32 value: 980 diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index 38db2e513bd7..75ea79d087a3 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -658,3 +658,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 43817c837412..82f6fc02dfb7 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -845,4 +845,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 000000000000..692c74b91793 --- /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 000000000000..3d8a7643b045 --- /dev/null +++ b/netwerk/dns/OnionAliasService.cpp @@ -0,0 +1,100 @@ +#include "torproject/OnionAliasService.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPrefs_browser.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::AutoWriteLock lock(mLock); + mOnionAliases.InsertOrUpdate(shortHostname, std::move(longHostname)); + return NS_OK; +} + +NS_IMETHODIMP +OnionAliasService::GetOnionAlias(const nsACString& aShortHostname, nsACString& aLongHostname) +{ + aLongHostname = aShortHostname; + if (mozilla::StaticPrefs::browser_urlbar_onionRewrites_enabled() && + StringEndsWith(aShortHostname, ".tor.onion"_ns)) { + nsAutoCString* alias = nullptr; + // We want to keep the string stored in the map alive at least until we + // finish to copy it to the output parameter. + mozilla::AutoReadLock lock(mLock); + 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::AutoWriteLock 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 000000000000..5809e4112e24 --- /dev/null +++ b/netwerk/dns/OnionAliasService.h @@ -0,0 +1,40 @@ +#ifndef OnionAliasService_h_ +#define OnionAliasService_h_ + +#include "IOnionAliasService.h" + +#include "mozilla/RWLock.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "ScopedNSSTypes.h" + +namespace torproject { + +class OnionAliasService final : public IOnionAliasService { +public: + NS_DECL_THREADSAFE_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::RWLock 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 73e7e375fd6d..e895c636bbb2 100644 --- a/netwerk/dns/effective_tld_names.dat +++ b/netwerk/dns/effective_tld_names.dat @@ -5568,6 +5568,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 5d81059ed89d..c82689a147ec 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -110,3 +110,7 @@ LOCAL_INCLUDES += [ ]
USE_LIBS += ["icu"] + +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 f9fc29552ace..a9dc8f9dde11 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 2df79a3bc11a..32621111e19e 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; @@ -909,6 +911,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 fa75bb918e7f..b2190ac393b5 100644 --- a/security/manager/ssl/SSLServerCertVerification.h +++ b/security/manager/ssl/SSLServerCertVerification.h @@ -133,7 +133,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 ec48d9276bde..6bf4f4c97d3b 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -212,6 +212,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.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit 132fd7cc5ad6362c544f988ea49905aa108fbe96 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/components/about/AboutRedirector.cpp | 64 ++++++++++++++++++++++++++++ browser/components/about/components.conf | 1 + 2 files changed, 65 insertions(+)
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index c3095c4bd3bb..05fec67c79e0 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -15,6 +15,11 @@ #include "mozilla/StaticPrefs_browser.h" #include "mozilla/dom/ContentChild.h"
+// For Tor Browser manual +#include "nsTHashSet.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/Omnijar.h" + namespace mozilla { namespace browser {
@@ -150,6 +155,11 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::IS_SECURE_CHROME_UI}, + // The correct URI must be obtained by GetManualChromeURI + {"manual", "about:blank", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | + nsIAboutModule::IS_SECURE_CHROME_UI}, };
static nsAutoCString GetAboutModuleName(nsIURI* aURI) { @@ -166,6 +176,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) { @@ -219,6 +275,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); @@ -277,6 +337,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 d35ad34ec8ba..527718871468 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -13,6 +13,7 @@ pages = [ 'logins', 'loginsimportreport', 'firefoxview', + 'manual', 'newtab', 'pocket-home', 'pocket-saved',
tor-commits@lists.torproject.org