tbb-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 18645 discussions

[Git][tpo/applications/tor-browser][base-browser-102.9.0esr-12.5-1] fixup! Bug 31740: Remove some unnecessary RemoteSettings instances
by Pier Angelo Vendrame (@pierov) 22 Mar '23
by Pier Angelo Vendrame (@pierov) 22 Mar '23
22 Mar '23
Pier Angelo Vendrame pushed to branch base-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
e7da6e7c by Pier Angelo Vendrame at 2023-03-22T11:23:57+00:00
fixup! Bug 31740: Remove some unnecessary RemoteSettings instances
Bug 40788: Tor Browser is phoning home
The hijack-list component is the only remaining one calling home.
This is a temporary workaround, to use the remote settings.
We should do something like this commit at a more generic level, for all
RemoteSettings users.
(cherry picked from commit 2a2e87c3dbc2a98a9ff6387dd278cb2643323b6d)
- - - - -
1 changed file:
- toolkit/modules/IgnoreLists.jsm
Changes:
=====================================
toolkit/modules/IgnoreLists.jsm
=====================================
@@ -8,19 +8,19 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
- RemoteSettings: "resource://services-settings/remote-settings.js",
RemoteSettingsClient: "resource://services-settings/RemoteSettingsClient.jsm",
});
-var EXPORTED_SYMBOLS = ["IgnoreLists"];
+Cu.importGlobalProperties(["fetch"]);
-const SETTINGS_IGNORELIST_KEY = "hijack-blocklists";
+var EXPORTED_SYMBOLS = ["IgnoreLists"];
class IgnoreListsManager {
+ _ignoreListSettings = null;
+
async init() {
- if (!this._ignoreListSettings) {
- this._ignoreListSettings = RemoteSettings(SETTINGS_IGNORELIST_KEY);
- }
+ // TODO: Restore the initialization, once we use only the local dumps for
+ // the remote settings.
}
async getAndSubscribe(listener) {
@@ -30,7 +30,7 @@ class IgnoreListsManager {
const settings = await this._getIgnoreList();
// Listen for future updates after we first get the values.
- this._ignoreListSettings.on("sync", listener);
+ this._ignoreListSettings?.on("sync", listener);
return settings;
}
@@ -70,6 +70,14 @@ class IgnoreListsManager {
* could be obtained.
*/
async _getIgnoreListSettings(firstTime = true) {
+ if (!this._ignoreListSettings) {
+ const dump = await fetch(
+ "resource:///defaults/settings/main/hijack-blocklists.json"
+ );
+ const { data } = await dump.json();
+ return data;
+ }
+
let result = [];
try {
result = await this._ignoreListSettings.get({
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/e7da6e7…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/e7da6e7…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-102.9.0esr-12.5-1] fixup! Bug 31740: Remove some unnecessary RemoteSettings instances
by Pier Angelo Vendrame (@pierov) 22 Mar '23
by Pier Angelo Vendrame (@pierov) 22 Mar '23
22 Mar '23
Pier Angelo Vendrame pushed to branch tor-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
2a2e87c3 by Pier Angelo Vendrame at 2023-03-22T12:22:38+01:00
fixup! Bug 31740: Remove some unnecessary RemoteSettings instances
Bug 40788: Tor Browser is phoning home
The hijack-list component is the only remaining one calling home.
This is a temporary workaround, to use the remote settings.
We should do something like this commit at a more generic level, for all
RemoteSettings users.
- - - - -
1 changed file:
- toolkit/modules/IgnoreLists.jsm
Changes:
=====================================
toolkit/modules/IgnoreLists.jsm
=====================================
@@ -8,19 +8,19 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
- RemoteSettings: "resource://services-settings/remote-settings.js",
RemoteSettingsClient: "resource://services-settings/RemoteSettingsClient.jsm",
});
-var EXPORTED_SYMBOLS = ["IgnoreLists"];
+Cu.importGlobalProperties(["fetch"]);
-const SETTINGS_IGNORELIST_KEY = "hijack-blocklists";
+var EXPORTED_SYMBOLS = ["IgnoreLists"];
class IgnoreListsManager {
+ _ignoreListSettings = null;
+
async init() {
- if (!this._ignoreListSettings) {
- this._ignoreListSettings = RemoteSettings(SETTINGS_IGNORELIST_KEY);
- }
+ // TODO: Restore the initialization, once we use only the local dumps for
+ // the remote settings.
}
async getAndSubscribe(listener) {
@@ -30,7 +30,7 @@ class IgnoreListsManager {
const settings = await this._getIgnoreList();
// Listen for future updates after we first get the values.
- this._ignoreListSettings.on("sync", listener);
+ this._ignoreListSettings?.on("sync", listener);
return settings;
}
@@ -70,6 +70,14 @@ class IgnoreListsManager {
* could be obtained.
*/
async _getIgnoreListSettings(firstTime = true) {
+ if (!this._ignoreListSettings) {
+ const dump = await fetch(
+ "resource:///defaults/settings/main/hijack-blocklists.json"
+ );
+ const { data } = await dump.json();
+ return data;
+ }
+
let result = [];
try {
result = await this._ignoreListSettings.get({
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/2a2e87c…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/2a2e87c…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build] Pushed new tag tbb-12.5a4-build1
by Richard Pospesel (@richard) 22 Mar '23
by Richard Pospesel (@richard) 22 Mar '23
22 Mar '23
Richard Pospesel pushed new tag tbb-12.5a4-build1 at The Tor Project / Applications / tor-browser-build
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/tree/tbb…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] 2 commits: fixup! Bug 40805: Use the version flag for all our browsers
by Richard Pospesel (@richard) 22 Mar '23
by Richard Pospesel (@richard) 22 Mar '23
22 Mar '23
Richard Pospesel pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
8b0e78e6 by Richard Pospesel at 2023-03-21T17:43:06+00:00
fixup! Bug 40805: Use the version flag for all our browsers
- - - - -
d0135ec6 by Richard Pospesel at 2023-03-21T17:43:06+00:00
Bug 40758: Prepare alpha release 12.5a4
- - - - -
11 changed files:
- projects/android-components/config
- projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
- projects/browser/allowed_addons.json
- projects/browser/config
- projects/fenix/config
- projects/firefox/config
- projects/geckoview/config
- projects/go/config
- projects/manual/config
- projects/translation/config
- rbm.conf
Changes:
=====================================
projects/android-components/config
=====================================
@@ -14,7 +14,7 @@ container:
var:
android_components_version: 102.0.14
browser_branch: 12.5-1
- android_components_build: 1
+ android_components_build: 2
# This should be updated when the list of gradle dependencies is changed.
gradle_dependencies_version: 37
gradle_version: 7.0.2
=====================================
projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
=====================================
@@ -1,3 +1,92 @@
+Tor Browser 12.5a4 - March 20 2023
+ * All Platforms
+ * Updated Translations
+ * Updated NoScript to 11.4.20
+ * Bug 40353: Re-enable rlbox [tor-browser-build]
+ * Bug 40810: Enable Finnish (fi) in alpha builds [tor-browser-build]
+ * Bug 40817: Add basebrowser-incrementals-nightly makefile target [tor-browser-build]
+ * Bug 41599: about:networking#networkid should be normalized [tor-browser]
+ * Bug 41635: Disable the Normandy component at compile time [tor-browser]
+ * Bug 41636: Disable back webextension.storage.sync after ensuring NoScript settings won't be lost [tor-browser]
+ * Bug 41646: Regression in 12.5a3: the system font patch should also set a font-size [tor-browser]
+ * Bug 41647: Turn --enable-base-browser in --with-base-browser-version [tor-browser]
+ * Bug 41659: Add canonical color definitions to base-browser [tor-browser]
+ * Bug 41662: Disable about:sync-logs [tor-browser]
+ * Bug 41670: Rebase Tor Browser Alpha to 102.9.0esr [tor-browser]
+ * Bug 41671: Turn media.peerconnection.ice.relay_only to true as defense in depth against WebRTC ICE leaks [tor-browser]
+ * Windows + macOS + Linux
+ * Updated Firefox to 102.9esr
+ * Bug 40144: about:privatebrowsing Firefox branding [tor-browser]
+ * Bug 40788: Remove all languages but en-US for privacy-browser build target [tor-browser-build]
+ * Bug 40808: Set update URL for nightly base-browser [tor-browser-build]
+ * Bug 41085: Refactor the UI to remove all bridges [tor-browser]
+ * Bug 41093: Users don't understand the purpose of bridge-moji [tor-browser]
+ * Bug 41574: Use --warning-color variable for the "Custom" label in the security level popup. [tor-browser]
+ * Bug 41657: Remove --enable-tor-browser-data-outside-app-dir [tor-browser]
+ * Windows
+ * Bug 40793: Add some metadata also to the Windows installer [tor-browser-build]
+ * Bug 40801: Correct the ExecShell for system-wide installs in the NSIS script [tor-browser-build]
+ * Android
+ * Updated GeckoView to 102.9esr
+ * Bug 40800: WebTunnel Integration in Tor Browser mobile [tor-browser-build]
+ * Bug 41667: Enable media.peerconnection.ice.obfuscate_host_addresses on Android for defense-in-depth [tor-browser]
+ * Bug 41677: Remove the --disable-tor-browser-update flag on Android [tor-browser]
+ * Build System
+ * All Platforms
+ * Updated Go to 1.19.7
+ * Bug 40750: Find why rlbox hurts reproducibility [tor-browser-build]
+ * Bug 40763: Add support for signing multiple browsers in tools/signing/nightly [tor-browser-build]
+ * Bug 40794: Include the build-id in firefox-l10n output name [tor-browser-build]
+ * Bug 40795: Trim down tor-browser-build release prep issue templates [tor-browser-build]
+ * Bug 40796: Bad UX for the changelogs script when using the issue number [tor-browser-build]
+ * Bug 40805: Define the version flag for all browsers [tor-browser-build]
+ * Bug 40807: Add config for signing base-browser nightly in tools/signing/nightly [tor-browser-build]
+ * Bug 40812: Make var/rezip in projects/firefox/config quiet [tor-browser-build]
+ * Bug 41649: Create rebase and security backport gitlab issue templates [tor-browser]
+ * Bug 41682: Add base-browser nightly mar signing key [tor-browser]
+ * Windows + macOS + Linux
+ * Bug 40809: Remove --enable-tor-browser-update and --enable-verify-mar from projects/firefox/mozconfig [tor-browser-build]
+ * Bug 40813: Enable var/updater_enabled for basebrowser nightly [tor-browser-build]
+ * macOS
+ * Bug 40790: Fix dmg2mar after dmg changes from #28124 [tor-browser-build]
+ * Bug 40791: tools/dmg2mar should exit with an error when there is an error creating the mar file [tor-browser-build]
+ * Bug 40792: signing scripts missing project name prefix to make rule [tor-browser-build]
+ * Bug 40798: dmg2mar step also takes care of copying the signed+stabled dmg to the signed directory [tor-browser-build]
+ * Bug 40806: Update the reference to the macOS mozconfig [tor-browser-build]
+ * Bug 41453: Rename mozconfig-macos-x86_64 to mozconfig-macos [tor-browser]
+ * Android
+ * Bug 40789: Broken mirror links for glean: link 404 for version 5.0.1 hosted at aguestuser's tor people storage [tor-browser-build]
+
+Tor Browser 12.0.4 - March 13 2023
+ * All Platforms
+ * Updated Translations
+ * Updated NoScript to 11.4.18
+ * Bug 41598: Prevent NoScript from being removed / disabled until core functionality has been migrated to Tor Browser [tor-browser]
+ * Bug 41603: Customize the creation of MOZ_SOURCE_URL [tor-browser]
+ * Bug 41627: Enable network.http.referer.hideOnionSource in base-browser [tor-browser]
+ * Bug 41637: cherry-pick Mozilla 1814416: Generalize the app name in about:buildconfig. r=ahochheiden [tor-browser]
+ * Bug 41659: Add canonical color definitions to base-browser [tor-browser]
+ * Bug 41669: Rebase Tor Browser stable to 102.9.0esr [tor-browser]
+ * Windows + macOS + Linux
+ * Updated Firefox to 102.9esr
+ * Bug 41542: Disable the creation of a default profile [tor-browser]
+ * Bug 41574: Use --warning-color variable for the "Custom" label in the security level popup. [tor-browser]
+ * Bug 41606: Move the changes to the hamburger menu out of the Torbutton commit [tor-browser]
+ * Bug 41626: Bridge-emojii tooltips not localized in ES locale [tor-browser]
+ * Android
+ * Updated GeckoView to 102.9esr
+ * Bug 41679: Backport Android-specific security fixes from Firefox 111 to ESR 102.9-based Tor Browser [tor-browser]
+ * Build System
+ * All Platforms
+ * Updated Go to 1.19.7
+ * Bug 40764: Embed repo URL and git revision in Firefox [tor-browser-build]
+ * Bug 40782: Update tools/signing/download-unsigned-sha256sums-gpg-signatures-from-people-tpo to fetch from tb-build-04 and tb-build-05 [tor-browser-build]
+ * macOS
+ * Bug 40790: Fix dmg2mar after dmg changes from #28124 [tor-browser-build]
+ * Bug 40791: tools/dmg2mar should exit with an error when there is an error creating the mar file [tor-browser-build]
+ * Android
+ * Bug 40789: Broken mirror links for glean: link 404 for version 5.0.1 hosted at aguestuser's tor people storage [tor-browser-build]
+
Tor Browser 12.5a3 - February 16 2023
* All Platforms
* Updated Translations
=====================================
projects/browser/allowed_addons.json
=====================================
@@ -17,7 +17,7 @@
"picture_url": "https://addons.mozilla.org/user-media/userpics/34/9734/13299734/13299734.pn…"
}
],
- "average_daily_users": 969882,
+ "average_daily_users": 991330,
"categories": {
"android": [
"experimental",
@@ -50,7 +50,7 @@
"name": {
"en-US": "The MIT License"
},
- "url": "http://www.opensource.org/licenses/mit-license.php"
+ "url": "http://www.opensource.org/license/mit"
},
"release_notes": {
"en-US": "- Fixed a edge case with extracting color numbers, it's now able to extract `rgb(0 0 0/0.04)`.\n- Improved IPv6 check.\n- Faster UI loading.\n- Users' fixes for websites."
@@ -77,7 +77,8 @@
"theme",
"<all_urls>"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -220,10 +221,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.5586,
- "bayesian_average": 4.557409514018636,
- "count": 4859,
- "text_count": 1543
+ "average": 4.5572,
+ "bayesian_average": 4.556020910333306,
+ "count": 4912,
+ "text_count": 1556
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/darkreader/reviews/",
"requires_payment": false,
@@ -320,7 +321,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/darkreader/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/darkreader/versions/",
- "weekly_downloads": 28911
+ "weekly_downloads": 29096
},
"notes": null
},
@@ -336,7 +337,7 @@
"picture_url": "https://addons.mozilla.org/user-media/userpics/56/7656/6937656/6937656.png?…"
}
],
- "average_daily_users": 265173,
+ "average_daily_users": 266501,
"categories": {
"android": [
"security-privacy"
@@ -396,7 +397,8 @@
"webRequest",
"webRequestBlocking"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -551,10 +553,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.816,
- "bayesian_average": 4.81132997570714,
- "count": 1321,
- "text_count": 235
+ "average": 4.8173,
+ "bayesian_average": 4.812651456820935,
+ "count": 1330,
+ "text_count": 236
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/decentraleyes/reviews/",
"requires_payment": false,
@@ -639,7 +641,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/decentraleyes/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/decentraleyes/versions/",
- "weekly_downloads": 4626
+ "weekly_downloads": 4398
},
"notes": null
},
@@ -655,7 +657,7 @@
"picture_url": "https://addons.mozilla.org/user-media/userpics/73/4073/5474073/5474073.png?…"
}
],
- "average_daily_users": 1096532,
+ "average_daily_users": 1141854,
"categories": {
"android": [
"security-privacy"
@@ -1105,7 +1107,8 @@
"http://docs.google.com/*",
"<all_urls>"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -1177,10 +1180,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.7987,
- "bayesian_average": 4.795881600312439,
- "count": 2181,
- "text_count": 427
+ "average": 4.8016,
+ "bayesian_average": 4.798796787815379,
+ "count": 2198,
+ "text_count": 426
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/privacy-badger17/reviews/",
"requires_payment": false,
@@ -1204,7 +1207,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/privacy-badger17/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/privacy-badger17/versions/",
- "weekly_downloads": 18674
+ "weekly_downloads": 41171
},
"notes": null
},
@@ -1220,7 +1223,7 @@
"picture_url": null
}
],
- "average_daily_users": 6382932,
+ "average_daily_users": 6481174,
"categories": {
"android": [
"security-privacy"
@@ -1232,18 +1235,18 @@
"contributions_url": "",
"created": "2015-04-25T07:26:22Z",
"current_version": {
- "id": 5525624,
+ "id": 5534721,
"compatibility": {
"firefox": {
- "min": "68.0",
+ "min": "78.0",
"max": "*"
},
"android": {
- "min": "68.0",
+ "min": "79.0",
"max": "*"
}
},
- "edit_url": "https://addons.mozilla.org/en-US/developers/addon/ublock-origin/versions/55…",
+ "edit_url": "https://addons.mozilla.org/en-US/developers/addon/ublock-origin/versions/55…",
"is_strict_compatibility_enabled": false,
"license": {
"id": 6,
@@ -1254,22 +1257,22 @@
"url": "http://www.gnu.org/licenses/gpl-3.0.html"
},
"release_notes": {
- "en-US": "See complete release notes for <a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/b140bd014e7f181fb45b37…" rel=\"nofollow\">1.47.0</a>.\n\n<b>Fixes / changes</b>\n\n<ul><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/e35dbd5e9f783a5823861d…" rel=\"nofollow\">Prevent context menu entries on non-HTTP documents</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/8574183ea43238e380567b…" rel=\"nofollow\">Ignore compilation hints when applying exception cosmetic filters</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/58c3f94f487f1a98eda31a…" rel=\"nofollow\"> Add support for regex-based values as target domain for static extended filters</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/664a38f09616854a9936e1…" rel=\"nofollow\">Add support for regex-based values for <code>domain=</code>/<code>from=</code>/<code>to=</code> options</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/250586255e6c8ce1e3ae5e…" rel=\"nofollow\">Rewrite static filtering parser</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/e628bc12d0be3b68739917…" rel=\"nofollow\">uBO doesn't reject cosmetic filters with invalid pseudo-classes/pseudo-elements</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/01f40a6478c16320eebcb5…" rel=\"nofollow\">Make the logger entry dialog modeless</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/8e5b045c6bbda00f272ead…" rel=\"nofollow\">Fix missing regex flags</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/338ff06b790a9831939536…" rel=\"nofollow\">Import version 1.2.0 of RegexAnalyzer</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/1ad5b3aa657299b66b4e93…" rel=\"nofollow\">Set <code>charset</code> to <code>utf-8</code> for Blob-based injected scriptlets </a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/e5547c21fa5e38fe56d4d8…" rel=\"nofollow\">Disallow <code>-abp-...</code> filters if not using <code>#?#</code></a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/e94131c76bb7a1a2513a70…" rel=\"nofollow\">Work related to keyboard shortcuts for page reload</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/5fb5dddef3045ae7f30a24…" rel=\"nofollow\">New network filter option: <code>to=[list of domain names]</code></a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/78eda79469d3baaeb94f09…" rel=\"nofollow\">New network filter option: <code>method=...</code></a></li></ul>\n<a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/0706aff990bd228e1bb5b0…" rel=\"nofollow\">Commits history since last version</a>."
+ "en-US": "See complete release notes for <a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/ea467320fde698dd660f5e…" rel=\"nofollow\">1.47.4</a>.\n\n<b>Fixes / changes</b>\n\n<ul><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/90332d429df9e9702298e1…" rel=\"nofollow\">Add list for Macedonia; add languages to Persian list</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/f99dde6a629e89c9f720b2…" rel=\"nofollow\">Support update period below 1-day</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/07f7b3b6b630e4e4652b00…" rel=\"nofollow\">Restore behind-the-scene origin for docless secondary resources</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/538445e0e1306d365a45a8…" rel=\"nofollow\">Refine AST template's regex for hosts file entries</a></li><li><a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/4b7349389c22c2bd89ddd6…" rel=\"nofollow\">Flush cached result when adding to FilterDomainHitSet</a></li></ul>\n<a href=\"https://prod.outgoing.prod.webservices.mozgcp.net/v1/25aa2ca137134570435ed8…" rel=\"nofollow\">Commits history since last version</a>."
},
- "reviewed": "2023-02-15T10:52:52Z",
- "version": "1.47.0",
+ "reviewed": "2023-03-08T13:21:02Z",
+ "version": "1.47.4",
"files": [
{
- "id": 4069969,
- "created": "2023-02-13T18:12:01Z",
- "hash": "sha256:e76958a8c8eb4b71954dac9b0eb1f5a7a545f746159227e0b821b1a1b23f7cd4",
+ "id": 4079064,
+ "created": "2023-03-03T22:26:56Z",
+ "hash": "sha256:a35a6c8758ba74616afc09648c96d74ec2e7d27fe30f311d1db6e9a4966e7858",
"is_restart_required": false,
"is_webextension": true,
"is_mozilla_signed_extension": false,
"platform": "all",
- "size": 3224904,
+ "size": 3246151,
"status": "public",
- "url": "https://addons.mozilla.org/firefox/downloads/file/4069969/ublock_origin-1.4…",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4079064/ublock_origin-1.4…",
"permissions": [
"dns",
"menus",
@@ -1292,7 +1295,8 @@
"https://*.github.io/*",
"https://*.letsblock.it/*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -1384,7 +1388,7 @@
},
"is_disabled": false,
"is_experimental": false,
- "last_updated": "2023-02-15T10:52:52Z",
+ "last_updated": "2023-03-19T11:30:30Z",
"name": {
"ar": "uBlock Origin",
"bg": "uBlock Origin",
@@ -1529,10 +1533,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.7797,
- "bayesian_average": 4.779290903491396,
- "count": 14964,
- "text_count": 3911
+ "average": 4.7805,
+ "bayesian_average": 4.780094594455903,
+ "count": 15128,
+ "text_count": 3934
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/reviews/",
"requires_payment": false,
@@ -1594,7 +1598,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/versions/",
- "weekly_downloads": 146451
+ "weekly_downloads": 143423
},
"notes": null
},
@@ -1610,7 +1614,7 @@
"picture_url": null
}
],
- "average_daily_users": 155053,
+ "average_daily_users": 159499,
"categories": {
"android": [
"photos-media"
@@ -1641,7 +1645,7 @@
"name": {
"en-US": "The MIT License"
},
- "url": "http://www.opensource.org/licenses/mit-license.php"
+ "url": "http://www.opensource.org/license/mit"
},
"release_notes": {
"de": "Experimentelle Verbesserungen der Handhabung von Youtube.",
@@ -1667,7 +1671,8 @@
"*://*.youtube-nocookie.com/*",
"*://*.vimeo.com/*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -1707,10 +1712,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.5083,
- "bayesian_average": 4.503059203963862,
- "count": 1088,
- "text_count": 403
+ "average": 4.5055,
+ "bayesian_average": 4.50026668386193,
+ "count": 1090,
+ "text_count": 405
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/video-background-play-fix/re…",
"requires_payment": false,
@@ -1732,7 +1737,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/video-background-play-fix/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/video-background-play-fix/ve…",
- "weekly_downloads": 503
+ "weekly_downloads": 363
},
"notes": null
},
@@ -1748,7 +1753,7 @@
"picture_url": null
}
],
- "average_daily_users": 92798,
+ "average_daily_users": 92182,
"categories": {
"android": [
"experimental",
@@ -1812,7 +1817,8 @@
"*://twitter.com/*",
"*://tweetdeck.twitter.com/*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -1860,10 +1866,10 @@
],
"promoted": null,
"ratings": {
- "average": 4.3838,
- "bayesian_average": 4.369924660120627,
- "count": 396,
- "text_count": 111
+ "average": 4.3769,
+ "bayesian_average": 4.3630961936527894,
+ "count": 398,
+ "text_count": 112
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/privacy-possum/reviews/",
"requires_payment": false,
@@ -1885,7 +1891,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/privacy-possum/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/privacy-possum/versions/",
- "weekly_downloads": 486
+ "weekly_downloads": 1406
},
"notes": null
},
@@ -1901,7 +1907,7 @@
"picture_url": "https://addons.mozilla.org/user-media/userpics/64/9064/12929064/12929064.pn…"
}
],
- "average_daily_users": 249917,
+ "average_daily_users": 259947,
"categories": {
"android": [
"photos-media",
@@ -1969,7 +1975,8 @@
"https://*/*",
"file:///*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -2119,10 +2126,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.6512,
- "bayesian_average": 4.646376429373567,
- "count": 1227,
- "text_count": 237
+ "average": 4.6513,
+ "bayesian_average": 4.646506092845788,
+ "count": 1236,
+ "text_count": 240
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/search_by_image/reviews/",
"requires_payment": false,
@@ -2143,7 +2150,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/search_by_image/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/search_by_image/versions/",
- "weekly_downloads": 3780
+ "weekly_downloads": 7883
},
"notes": null
},
@@ -2166,7 +2173,7 @@
"picture_url": null
}
],
- "average_daily_users": 106756,
+ "average_daily_users": 109654,
"categories": {
"android": [
"other"
@@ -2412,7 +2419,8 @@
"*://*.google.cat/*",
"*://*.google.ng/*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -2448,10 +2456,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.4409,
- "bayesian_average": 4.4362065782583375,
- "count": 1193,
- "text_count": 317
+ "average": 4.4426,
+ "bayesian_average": 4.437933242019955,
+ "count": 1202,
+ "text_count": 319
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/google-search-fixer/reviews/",
"requires_payment": false,
@@ -2471,7 +2479,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/google-search-fixer/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/google-search-fixer/versions/",
- "weekly_downloads": 41
+ "weekly_downloads": 50
},
"notes": null
},
@@ -2487,7 +2495,7 @@
"picture_url": "https://addons.mozilla.org/user-media/userpics/43/0143/143/143.png?modified…"
}
],
- "average_daily_users": 329326,
+ "average_daily_users": 326501,
"categories": {
"android": [
"performance",
@@ -2501,7 +2509,7 @@
"contributions_url": "https://www.paypal.com/donate/?hosted_button_id=9ERKTU5MBH4EW&utm_content=p…",
"created": "2005-05-13T10:51:32Z",
"current_version": {
- "id": 5523128,
+ "id": 5542185,
"compatibility": {
"firefox": {
"min": "59.0",
@@ -2512,7 +2520,7 @@
"max": "*"
}
},
- "edit_url": "https://addons.mozilla.org/en-US/developers/addon/noscript/versions/5523128",
+ "edit_url": "https://addons.mozilla.org/en-US/developers/addon/noscript/versions/5542185",
"is_strict_compatibility_enabled": false,
"license": {
"id": 13,
@@ -2523,22 +2531,22 @@
"url": "http://www.gnu.org/licenses/gpl-2.0.html"
},
"release_notes": {
- "en-US": "v 11.4.16\n============================================================\nx [L10n] Updated de, nl, pl, ru, sq, zh_CN\nx Always open the windowed standalone UI when invoked from\n the Alt+Shift+N shortcut\nx Alt+Shift+Space shortcut to toggle restrictions\n enforcement for current tab (issue #129, thanks PF4Public\n for RFE)"
+ "en-US": "v 11.4.20\n============================================================\nx Generalized prompt safety hooks\nx Better blob: URL support\nx [nscl] Improved cross-window patch cascading\nx [nscl] Avoid unneeded side effects when checking for\n zombie patched objects\nx [nscl] Prompt safety hooks\nx [L10n] Updated fr, fi\nx Fix font family typo (!283, thanks alex-kinokon)"
},
- "reviewed": "2023-02-09T17:48:06Z",
- "version": "11.4.16",
+ "reviewed": "2023-03-21T12:03:50Z",
+ "version": "11.4.20",
"files": [
{
- "id": 4067473,
- "created": "2023-02-06T22:12:36Z",
- "hash": "sha256:44bdddd89bee11e52e09ea967aebd3aa996dc2d66c1a819e8dfdaf9a16cc753b",
+ "id": 4086528,
+ "created": "2023-03-20T22:48:31Z",
+ "hash": "sha256:014aec3ce142222338372d227eaadfa2ae13c9e3861f6d43869615b932ce4b53",
"is_restart_required": false,
"is_webextension": true,
"is_mozilla_signed_extension": false,
"platform": "all",
- "size": 949002,
+ "size": 951165,
"status": "public",
- "url": "https://addons.mozilla.org/firefox/downloads/file/4067473/noscript-11.4.16.…",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4086528/noscript-11.4.20.…",
"permissions": [
"contextMenus",
"storage",
@@ -2552,7 +2560,8 @@
"file://*/*",
"ftp://*/*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -2604,7 +2613,7 @@
},
"is_disabled": false,
"is_experimental": false,
- "last_updated": "2023-02-09T17:48:06Z",
+ "last_updated": "2023-03-21T12:03:50Z",
"name": {
"de": "NoScript",
"el": "NoScript",
@@ -2676,10 +2685,10 @@
"category": "recommended"
},
"ratings": {
- "average": 4.4094,
- "bayesian_average": 4.4066726918104555,
- "count": 2037,
- "text_count": 793
+ "average": 4.405,
+ "bayesian_average": 4.402291645516784,
+ "count": 2052,
+ "text_count": 801
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/noscript/reviews/",
"requires_payment": false,
@@ -2723,7 +2732,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/noscript/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/noscript/versions/",
- "weekly_downloads": 7901
+ "weekly_downloads": 8165
},
"notes": null
},
@@ -2739,7 +2748,7 @@
"picture_url": null
}
],
- "average_daily_users": 145846,
+ "average_daily_users": 148495,
"categories": {
"android": [
"performance",
@@ -2800,7 +2809,8 @@
"<all_urls>",
"*://*.youtube.com/*"
],
- "optional_permissions": []
+ "optional_permissions": [],
+ "host_permissions": []
}
]
},
@@ -2853,10 +2863,10 @@
"category": "recommended"
},
"ratings": {
- "average": 3.9213,
- "bayesian_average": 3.91694973530769,
- "count": 1106,
- "text_count": 395
+ "average": 3.9155,
+ "bayesian_average": 3.9111787696510967,
+ "count": 1113,
+ "text_count": 396
},
"ratings_url": "https://addons.mozilla.org/en-US/firefox/addon/youtube-high-definition/revi…",
"requires_payment": false,
@@ -2875,7 +2885,7 @@
"type": "extension",
"url": "https://addons.mozilla.org/en-US/firefox/addon/youtube-high-definition/",
"versions_url": "https://addons.mozilla.org/en-US/firefox/addon/youtube-high-definition/vers…",
- "weekly_downloads": 1934
+ "weekly_downloads": 1769
},
"notes": null
}
=====================================
projects/browser/config
=====================================
@@ -103,9 +103,9 @@ input_files:
enable: '[% ! c("var/android") %]'
- filename: Bundle-Data
enable: '[% ! c("var/android") %]'
- - URL: https://addons.mozilla.org/firefox/downloads/file/4067473/noscript-11.4.16.…
+ - URL: https://addons.mozilla.org/firefox/downloads/file/4086528/noscript-11.4.20.…
name: noscript
- sha256sum: 44bdddd89bee11e52e09ea967aebd3aa996dc2d66c1a819e8dfdaf9a16cc753b
+ sha256sum: 014aec3ce142222338372d227eaadfa2ae13c9e3861f6d43869615b932ce4b53
- URL: https://addons.mozilla.org/firefox/downloads/file/4047353/ublock_origin-1.4…
name: ublock-origin
sha256sum: 6bf8af5266353fab5eabdc7476de026e01edfb7901b0430c5e539f6791f1edc8
=====================================
projects/fenix/config
=====================================
@@ -14,7 +14,7 @@ container:
var:
fenix_version: 102.2.1
browser_branch: 12.5-1
- fenix_build: 2
+ fenix_build: 3
git_branch: 'tor-browser-[% c("var/fenix_version") %]-[% c("var/browser_branch") %]'
copyright_year: '[% exec("git show -s --format=%ci").remove("-.*") %]'
# This should be updated when the list of gradle dependencies is changed.
=====================================
projects/firefox/config
=====================================
@@ -11,11 +11,11 @@ container:
use_container: 1
var:
- firefox_platform_version: 102.8.0
+ firefox_platform_version: 102.9.0
firefox_version: '[% c("var/firefox_platform_version") %]esr'
browser_series: '12.5'
browser_branch: '[% c("var/browser_series") %]-1'
- browser_build: 3
+ browser_build: 2
branding_directory_prefix: 'tb'
copyright_year: '[% exec("git show -s --format=%ci").remove("-.*") %]'
nightly_updates_publish_dir: '[% c("var/nightly_updates_publish_dir_prefix") %][% c("var/osname") %]'
=====================================
projects/geckoview/config
=====================================
@@ -11,9 +11,9 @@ container:
use_container: 1
var:
- geckoview_version: 102.8.0esr
+ geckoview_version: 102.9.0esr
browser_branch: 12.5-1
- browser_build: 3
+ browser_build: 2
copyright_year: '[% exec("git show -s --format=%ci").remove("-.*") %]'
deps:
- build-essential
@@ -91,7 +91,7 @@ steps:
mkdir "$HOME/.mozbuild"
# We still need to specify --tor-browser-version due to bug 34005.
./mach configure \
- --with-tor-browser-version=[% c("var/torbrowser_version") %] \
+ --with-base-browser-version=[% c("var/torbrowser_version") %] \
[% IF !c("var/rlbox") -%]--without-wasm-sandboxed-libraries[% END %]
./mach build --verbose
=====================================
projects/go/config
=====================================
@@ -1,5 +1,5 @@
# vim: filetype=yaml sw=2
-version: 1.19.6
+version: 1.19.7
filename: '[% project %]-[% c("version") %]-[% c("var/build_id") %].tar.gz'
container:
use_container: 1
@@ -121,7 +121,7 @@ input_files:
enable: '[% ! c("var/linux") %]'
- URL: 'https://golang.org/dl/go[% c("version") %].src.tar.gz'
name: go
- sha256sum: d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767
+ sha256sum: 775bdf285ceaba940da8a2fe20122500efd7a0b65dbcee85247854a8d7402633
- URL: 'https://golang.org/dl/go[% c("var/go14_version") %].src.tar.gz'
name: go14
sha256sum: 9947fc705b0b841b5938c48b22dc33e9647ec0752bae66e50278df4f23f64959
=====================================
projects/manual/config
=====================================
@@ -1,7 +1,7 @@
# vim: filetype=yaml sw=2
# To update, see doc/how-to-update-the-manual.txt
# Remember to update also the package's hash, with the version!
-version: 66168
+version: 69998
filename: 'manual-[% c("version") %]-[% c("var/build_id") %].tar.gz'
container:
use_container: 1
@@ -19,6 +19,6 @@ input_files:
- project: container-image
- URL: 'https://people.torproject.org/~richard/tbb_files/manual_[% c("version") %].zip'
name: manual
- sha256sum: efb88be6217f7cb6879157678963bc90c47a2b91380ae3ba9a52d748eabf9568
+ sha256sum: 236ad8b877d9bdb3ad5309deee8acb4025e10e4faf4626d063cbf6c87fc209a4
- filename: packagemanual.py
name: package_script
=====================================
projects/translation/config
=====================================
@@ -6,19 +6,19 @@ version: '[% c("abbrev") %]'
steps:
base-browser:
base-browser: '[% INCLUDE build %]'
- git_hash: d862d323bd78e100de5f51f2f8742d21de3a76d0
+ git_hash: 50ec65bc65e510156c1e9f5c835eb6ea034450d0
targets:
nightly:
git_hash: 'base-browser'
base-browser-fluent:
base-browser-fluent: '[% INCLUDE build %]'
- git_hash: 71869a9fd722c36eb57d08d7ef5a1fa5270ddb54
+ git_hash: 667a6daeba46b7aeb0a8b664bd9ae006f8ac233d
targets:
nightly:
git_hash: 'basebrowser-newidentityftl'
tor-browser:
tor-browser: '[% INCLUDE build %]'
- git_hash: b3d9033d2229e82d30771aa7a1b77667d9a80628
+ git_hash: e9ad920fc7427ff1b84e3723a97212ccd78dfe26
targets:
nightly:
git_hash: 'tor-browser'
@@ -26,7 +26,7 @@ steps:
fenix: '[% INCLUDE build %]'
# We need to bump the commit before releasing but just pointing to a branch
# might cause too much rebuidling of the Firefox part.
- git_hash: d19129475bae8943d3ee4ca6b6ed114a9a6db94e
+ git_hash: f9fd1869f6904e3a9fe99fa3626382b20ff33e66
targets:
nightly:
git_hash: 'fenix-torbrowserstringsxml'
=====================================
rbm.conf
=====================================
@@ -71,10 +71,10 @@ buildconf:
git_signtag_opt: '-s'
var:
- torbrowser_version: '12.5a3'
+ torbrowser_version: '12.5a4'
torbrowser_build: 'build1'
torbrowser_incremental_from:
- - 12.5a2
+ - 12.5a3
updater_enabled: 1
build_mar: 1
# By default, we sort the list of installed packages. This allows sharing
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/fenix][tor-browser-102.2.1-12.5-1] fixup! Modify build system
by ma1 (@ma1) 21 Mar '23
by ma1 (@ma1) 21 Mar '23
21 Mar '23
ma1 pushed to branch tor-browser-102.2.1-12.5-1 at The Tor Project / Applications / fenix
Commits:
5c26f235 by Pier Angelo Vendrame at 2023-03-21T11:14:21+01:00
fixup! Modify build system
Bug 41684: Android improvements for local dev builds
Added a script to fetch the Tor dependencies from tor-browser-build,
and print the name of the signed apk.
- - - - -
2 changed files:
- + tools/tba-fetch-deps.sh
- tools/tba-sign-devbuilds.sh
Changes:
=====================================
tools/tba-fetch-deps.sh
=====================================
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+cd "$(dirname $(realpath "$0"))/.."
+
+if [ -z "$TOR_BROWSER_BUILD" ]; then
+ TOR_BROWSER_BUILD=../tor-browser-build
+fi
+
+android_service="$(ls -1td "$TOR_BROWSER_BUILD/out/tor-android-service/"tor-android-service-* | head -1)"
+if [ -z "$android_service" ]; then
+ echo "Cannot find Tor Android Service artifacts!"
+ exit 1
+fi
+
+onion_proxy_library="$(ls -1td "$TOR_BROWSER_BUILD/out/tor-onion-proxy-library/"tor-onion-proxy-library-* | head -1)"
+if [ -z "$onion_proxy_library" ]; then
+ echo "Cannot find Tor Onoin Proxy library artifacts!"
+ exit 2
+fi
+
+cp "$android_service"/* app/
+cp "$onion_proxy_library"/* app/
=====================================
tools/tba-sign-devbuilds.sh
=====================================
@@ -38,6 +38,7 @@ sign () {
rm -f "$aligned"
"$zipalign" -p 4 "$apk" "$aligned"
"$apksigner" sign --ks "$key" --in "$aligned" --out "$out" --ks-key-alias androidqakey --key-pass pass:android --ks-pass pass:android
+ echo "Signed $out"
}
for apk in app/build/outputs/apk/release/*-unsigned.apk; do
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/commit/5c26f2353ef1a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/commit/5c26f2353ef1a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-102.9.0esr-12.5-1] fixup! Firefox preference overrides.
by Richard Pospesel (@richard) 21 Mar '23
by Richard Pospesel (@richard) 21 Mar '23
21 Mar '23
Richard Pospesel pushed to branch base-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
566eaec6 by Pier Angelo Vendrame at 2023-03-21T12:41:17+00:00
fixup! Firefox preference overrides.
Bug 41683: Disable the network process on Windows
- - - - -
1 changed file:
- browser/app/profile/001-base-profile.js
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -433,6 +433,14 @@ pref("captivedetect.canonicalURL", "");
// See tor-browser#18801.
pref("dom.push.serverURL", "");
+#ifdef XP_WIN
+// tor-browser#41683: Disable the network process on Windows
+// Mozilla already disables the network process for HTTP.
+// With this preference, we completely disable it, because we found that it
+// breaks stuff with mingw. See also tor-browser#41489.
+pref("network.process.enabled", false);
+#endif
+
// Extension support
pref("extensions.autoDisableScopes", 0);
pref("extensions.databaseSchema", 3);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/566eaec…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/566eaec…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-102.9.0esr-12.5-1] fixup! Firefox preference overrides.
by Richard Pospesel (@richard) 21 Mar '23
by Richard Pospesel (@richard) 21 Mar '23
21 Mar '23
Richard Pospesel pushed to branch tor-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
a7769330 by Pier Angelo Vendrame at 2023-03-20T18:58:51+01:00
fixup! Firefox preference overrides.
Bug 41683: Disable the network process on Windows
- - - - -
1 changed file:
- browser/app/profile/001-base-profile.js
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -429,6 +429,14 @@ pref("captivedetect.canonicalURL", "");
// See tor-browser#18801.
pref("dom.push.serverURL", "");
+#ifdef XP_WIN
+// tor-browser#41683: Disable the network process on Windows
+// Mozilla already disables the network process for HTTP.
+// With this preference, we completely disable it, because we found that it
+// breaks stuff with mingw. See also tor-browser#41489.
+pref("network.process.enabled", false);
+#endif
+
// Extension support
pref("extensions.autoDisableScopes", 0);
pref("extensions.databaseSchema", 3);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/a776933…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/a776933…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-102.9.0esr-12.5-1] 12 commits: Bug 13379: Allow using NSS to sign and verify MAR signatures
by Pier Angelo Vendrame (@pierov) 20 Mar '23
by Pier Angelo Vendrame (@pierov) 20 Mar '23
20 Mar '23
Pier Angelo Vendrame pushed to branch base-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
9a95e213 by Kathy Brade at 2023-03-20T18:38:16+01:00
Bug 13379: Allow using NSS to sign and verify MAR signatures
Allow using NSS on all platforms for checking MAR signatures (instead
of using OS-native APIs, the default 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 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 can vary.
- - - - -
136c2813 by Pier Angelo Vendrame at 2023-03-20T18:38:25+01:00
fixup! Bug 13379: Allow using NSS to sign and verify MAR signatures
Bug 41668: Port some updater patches to Base Browser
Use a configure-time flag to force using NSS for MARs signatures.
- - - - -
72aa9358 by Pier Angelo Vendrame at 2023-03-20T18:38:25+01:00
fixup! Bug 13379: Allow using NSS to sign and verify MAR signatures
Bug 41668: Port some updater patches to Base Browser
Avoid using -rpath on macOS.
- - - - -
038b55d2 by Pier Angelo Vendrame at 2023-03-20T18:38:26+01:00
fixup! Base Browser's .mozconfigs.
Bug 41668: Port some updater patches to Base Browser
- - - - -
7ae822c3 by Pier Angelo Vendrame at 2023-03-20T18:38:26+01:00
fixup! Base Browser's .mozconfigs.
Bug 41677: Fix updater settings for Android
- - - - -
da0f77aa by Pier Angelo Vendrame at 2023-03-20T18:38:26+01:00
fixup! Firefox preference overrides.
Bug 41668: Port some updater patches to Base Browser
Do not change app.update.auto in firefox.js, but add it to 001.
- - - - -
b5f2300a by Kathy Brade at 2023-03-20T18:38:27+01:00
Bug 4234: Use the Firefox Update Process for Base Browser.
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 27221: purge the startup cache if the Base Browser
version changed (even if the Firefox version and build ID did
not change), e.g., after a minor Base 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 Base 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.
- - - - -
e359ab23 by Pier Angelo Vendrame at 2023-03-20T18:38:27+01:00
fixup! Bug 4234: Use the Firefox Update Process for Base Browser.
Bug 41647: Clean up our {TOR,BASE}_BROWSER(_VERSION)? macros
Changed TOR_BROWSER_VERSION with BASE_BROWSER_VERSION.
- - - - -
ef8cc25a by Pier Angelo Vendrame at 2023-03-20T18:38:28+01:00
fixup! Bug 4234: Use the Firefox Update Process for Base Browser.
Bug 41668: Port some updater patches to Base Browser
Move the check on the update package version to this other commit.
- - - - -
2ae0ca1c by Pier Angelo Vendrame at 2023-03-20T18:38:28+01:00
fixup! Bug 4234: Use the Firefox Update Process for Base Browser.
Bug 41668: Port some updater patches to Base Browser
Rename TOR_BROWSER_UPDATE to BASE_BROWSER_UPDATE.
Also, do not change app.update.auto in firefox.js, but add it to 001.
- - - - -
9640ba6e by Pier Angelo Vendrame at 2023-03-20T18:38:28+01:00
fixup! Bug 4234: Use the Firefox Update Process for Base Browser.
Bug 41668: Port some updater patches to Base Browser
Changes that will need to remain when actually cherry-picking to
base-browser, because they contain name fixes, for example.
Or, for UpdateService.jsm, they need to stay because they make the patch
more understandable.
- - - - -
9b61b6f0 by Pier Angelo Vendrame at 2023-03-20T18:38:29+01:00
fixup! Bug 4234: Use the Firefox Update Process for Base Browser.
Bug 41668: Port some updater patches to Base Browser
Remove Tor Browser-only additions to make the updater patches usable
with Base Browser.
This commit should be reverted, and become a commit on its own, to
further adapt the updater to Tor Browser needs.
- - - - -
30 changed files:
- browser/app/Makefile.in
- browser/app/profile/001-base-profile.js
- browser/app/profile/firefox.js
- browser/base/content/aboutDialog-appUpdater.js
- browser/base/content/aboutDialog.js
- browser/base/moz.build
- browser/components/BrowserContentHandler.jsm
- browser/components/customizableui/content/panelUI.inc.xhtml
- browser/components/preferences/main.js
- browser/config/mozconfigs/base-browser
- browser/config/mozconfigs/base-browser-android
- browser/confvars.sh
- browser/installer/Makefile.in
- browser/installer/package-manifest.in
- build/application.ini.in
- build/moz.configure/init.configure
- build/moz.configure/update-programs.configure
- devtools/client/aboutdebugging/src/actions/runtimes.js
- modules/libmar/tool/mar.c
- modules/libmar/tool/moz.build
- modules/libmar/verify/moz.build
- mozconfig-linux-x86_64-dev
- toolkit/modules/AppConstants.jsm
- toolkit/modules/UpdateUtils.jsm
- toolkit/modules/moz.build
- toolkit/mozapps/extensions/AddonManager.jsm
- toolkit/mozapps/extensions/test/browser/head.js
- toolkit/mozapps/extensions/test/xpcshell/head_addons.js
- toolkit/mozapps/update/UpdateService.jsm
- toolkit/mozapps/update/UpdateServiceStub.jsm
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/9c1dd5…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/9c1dd5…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] 2 commits: Bug 40763: Add support for signing multiple browsers in tools/signing/nightly
by boklm (@boklm) 20 Mar '23
by boklm (@boklm) 20 Mar '23
20 Mar '23
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
38099794 by Nicolas Vigier at 2023-03-20T17:29:45+01:00
Bug 40763: Add support for signing multiple browsers in tools/signing/nightly
- - - - -
3f0b4c83 by Nicolas Vigier at 2023-03-20T17:29:46+01:00
Bug 40807: Add config for basebrowser nightly signing
- - - - -
3 changed files:
- tools/signing/nightly/config.yml
- tools/signing/nightly/create-nightly-mar-signing-key
- tools/signing/nightly/sign-nightly
Changes:
=====================================
tools/signing/nightly/config.yml
=====================================
@@ -3,15 +3,22 @@ martools_version: 9.0.2
martools_url: https://archive.torproject.org/tor-package-archive/torbrowser/
martools_gpg_keyring: keyring/torbrowser.gpg
builds_url: /srv/tbb-nightlies-master.torproject.org/htdocs/nightly-builds/tor-browser-builds
-builds_url_auth_basic_username: tor-guest
-builds_url_auth_basic_password: tor-guest
-publish_dirs:
+torbrowser:
+ publish_dirs:
- nightly-linux-x86_64
- nightly-linux-i686
- nightly-windows-x86_64
- nightly-windows-i686
- nightly-macos
-nss_db_dir: nssdb
+ nss_db_dir: nssdb
+basebrowser:
+ publish_dirs:
+ - basebrowser-nightly-linux-x86_64
+ - basebrowser-nightly-linux-i686
+ - basebrowser-nightly-windows-x86_64
+ - basebrowser-nightly-windows-i686
+ - basebrowser-nightly-macos
+ nss_db_dir: nssdb-basebrowser-1
nss_certname: nightly-marsigner
gpg_keyring: keyring/torbrowser-nightly.gpg
rsync_dest: /srv/tbb-nightlies-master.torproject.org/htdocs/nightly-updates/
=====================================
tools/signing/nightly/create-nightly-mar-signing-key
=====================================
@@ -1,6 +1,13 @@
#!/bin/bash
set -e
-nssdb="$(dirname "$0")/nssdb"
+if test "$#" -ne 2; then
+ echo "Usage: $0 <nssdb-dir> <Browser Name>" >&2
+ echo >&2
+ echo "Example: $0 nssdb-basebrowser 'Base Browser'" >&2
+ exit 1
+fi
+nssdb="$(dirname "$0")/$1"
+BrowserName="$2"
if test -d $nssdb
then
echo "Error: $nssdb already exists" >&2
@@ -9,5 +16,5 @@ fi
mkdir -p $nssdb
chmod 700 $nssdb
certutil -d $nssdb -N --empty-password
-certutil -d $nssdb -S -x -g 4096 -Z SHA384 -n nightly-marsigner -s "CN=Tor Browser Nightly MAR signing key" -t,,
+certutil -d $nssdb -S -x -g 4096 -Z SHA384 -n nightly-marsigner -s "CN=$BrowserName Nightly MAR signing key" -t,,
certutil -d $nssdb -L -r -n nightly-marsigner -o $nssdb/nightly-marsigner.der
=====================================
tools/signing/nightly/sign-nightly
=====================================
@@ -33,13 +33,22 @@ exit_error "Missing config file: $FindBin::Bin/config.yml"
my $config = LoadFile("$FindBin::Bin/config.yml");
my $topdir = "$FindBin::Bin/../../..";
+exit_error "Usage: sign-nightly <project>" unless @ARGV == 1;
+my $project = $ARGV[0];
+
+sub get_config {
+ my ($name) = @_;
+ return $config->{$project}{$name} if defined $config->{$project}{$name};
+ return $config->{$name};
+}
+
{
no warnings 'redefine';
sub LWP::UserAgent::get_basic_credentials {
- if ($config->{builds_url_auth_basic_username}
- && $config->{builds_url_auth_basic_password}) {
- return ( $config->{builds_url_auth_basic_username},
- $config->{builds_url_auth_basic_password} );
+ if (get_config('builds_url_auth_basic_username')
+ && get_config('builds_url_auth_basic_password')) {
+ return ( get_config('builds_url_auth_basic_username'),
+ get_config('builds_url_auth_basic_password') );
}
return ();
}
@@ -51,7 +60,7 @@ sub print_time {
}
END {
- print_time "Exiting sign-nightly (pid: $$)\n";
+ print_time "Exiting sign-nightly (pid: $$, project: $project)\n" if $project;
}
sub run_alone {
@@ -71,10 +80,8 @@ END {
}
sub get_tmpdir {
- my ($config) = @_;
- return File::Temp->newdir($config->{tmp_dir} ?
- (DIR => $config->{tmp_dir})
- : ());
+ my $tmp_dir = get_config('tmp_dir');
+ return File::Temp->newdir($tmp_dir ? (DIR => $tmp_dir) : ());
}
sub basedir_path {
@@ -83,15 +90,16 @@ sub basedir_path {
}
sub get_last_build_version {
- my ($config, $publish_dir) = @_;
+ my ($publish_dir) = @_;
my $today = 'tbb-nightly.' . DateTime->now->ymd('.');
my @last_days;
for my $i (1..5) {
my $dt = DateTime->now - DateTime::Duration->new(days => $i);
push @last_days, 'tbb-nightly.' . $dt->ymd('.');
}
+ my $builds_url = get_config('builds_url');
for my $version ($today, @last_days) {
- my $url = "$config->{builds_url}/$version/$publish_dir/sha256sums-unsigned-build.incrementals.txt";
+ my $url = "$builds_url/$version/$publish_dir/sha256sums-unsigned-build.incrementals.txt";
if ($url =~ m|^/|) {
return $version if -f $url;
} else {
@@ -115,10 +123,10 @@ sub set_current_version {
}
sub get_new_version {
- my ($config, $publish_dir) = @_;
+ my ($publish_dir) = @_;
my $today = 'tbb-nightly.' . DateTime->now->ymd('.');
my $current_ver = get_current_version($publish_dir);
- my $last_ver = get_last_build_version($config, $publish_dir);
+ my $last_ver = get_last_build_version($publish_dir);
return $last_ver unless defined($current_ver);
return undef if $current_ver eq $today;
return undef unless defined($last_ver);
@@ -147,13 +155,13 @@ sub get_file_sha256sum {
}
sub fetch_version {
- my ($config, $publish_dir, $version) = @_;
- my $tmpdir = get_tmpdir($config);
- my $urldir = "$config->{builds_url}/$version/$publish_dir";
+ my ($publish_dir, $version) = @_;
+ my $tmpdir = get_tmpdir();
+ my $urldir = get_config('builds_url') . "/$version/$publish_dir";
my $destdir = "$topdir/nightly/$publish_dir/$version";
return if -d $destdir;
- my $gpg_keyring = basedir_path($config->{gpg_keyring}, $topdir);
+ my $gpg_keyring = basedir_path(get_config('gpg_keyring'), $topdir);
for my $file (qw/sha256sums-unsigned-build.txt sha256sums-unsigned-build.incrementals.txt/) {
my $url = "$urldir/$file";
exit_error "Error getting $url"
@@ -184,17 +192,17 @@ sub fetch_version {
}
sub setup_martools {
- my ($config) = @_;
- my $martools_dir = "$FindBin::Bin/mar-tools-$config->{martools_version}";
+ my $martools_dir = "$FindBin::Bin/mar-tools-" . get_config('martools_version');
if (! -d $martools_dir) {
my $file = "mar-tools-linux64.zip";
- my $url = "$config->{martools_url}/$config->{martools_version}/$file";
- my $tmpdir = get_tmpdir($config);
+ my $url = join('/', get_config('martools_url'),
+ get_config('martools_version'), $file);
+ my $tmpdir = get_tmpdir();
exit_error "Error downloading $url"
unless getstore($url, "$tmpdir/$file") == 200;
exit_error "Error downloading $url.asc"
unless getstore("$url.asc", "$tmpdir/$file.asc") == 200;
- my $gpg_keyring = basedir_path($config->{martools_gpg_keyring}, $topdir);
+ my $gpg_keyring = basedir_path(get_config('martools_gpg_keyring'), $topdir);
exit_error "Error checking gpg signature for $url"
if system('gpg', '--no-default-keyring', '--keyring', $gpg_keyring,
'--verify', "$tmpdir/$file.asc",
@@ -212,14 +220,14 @@ sub setup_martools {
}
sub sign_version {
- my ($config, $publish_dir, $version) = @_;
- setup_martools($config);
- my $nss_db_dir = basedir_path($config->{nss_db_dir}, $FindBin::Bin);
+ my ($publish_dir, $version) = @_;
+ setup_martools();
+ my $nss_db_dir = basedir_path(get_config('nss_db_dir'), $FindBin::Bin);
for my $marfile (path("$topdir/nightly/$publish_dir/$version")->children(qr/\.mar$/)) {
print "Signing $marfile\n";
exit_error "Error signing $marfile"
unless system('signmar', '-d', $nss_db_dir, '-n',
- $config->{nss_certname}, '-s', $marfile,
+ get_config('nss_certname'), '-s', $marfile,
"$marfile-signed") == 0;
move("$marfile-signed", $marfile);
}
@@ -232,7 +240,7 @@ sub get_buildinfos {
}
sub update_responses {
- my ($config, $publish_dir, $version) = @_;
+ my ($publish_dir, $version) = @_;
my $ur_config = LoadFile("$FindBin::Bin/update-responses-base-config.yml");
$ur_config->{download}{mars_url} .= "/$publish_dir";
$ur_config->{releases_dir} = "$topdir/nightly/$publish_dir";
@@ -253,7 +261,7 @@ sub update_responses {
}
sub remove_oldversions {
- my ($config, $publish_dir, $version) = @_;
+ my ($publish_dir, $version) = @_;
for my $dir (path("$topdir/nightly/$publish_dir")->children) {
my ($filename) = fileparse($dir);
next if $filename eq $version;
@@ -262,27 +270,27 @@ sub remove_oldversions {
}
sub sync_dest {
- my ($config) = @_;
exit_error "Error running rsync"
if system('rsync', '-aH', '--delete-after',
- "$topdir/nightly/", "$config->{rsync_dest}/");
- if ($config->{post_rsync_cmd}) {
- exit_error "Error running $config->{post_rsync_cmd}"
- if system($config->{post_rsync_cmd});
+ "$topdir/nightly/", get_config('rsync_dest') . '/');
+ my $post_rsync_cmd = get_config('post_rsync_cmd');
+ if ($post_rsync_cmd) {
+ exit_error "Error running $post_rsync_cmd"
+ if system($post_rsync_cmd);
}
}
-print_time "Starting sign-nightly (pid: $$)\n";
+print_time "Starting sign-nightly (pid: $$, project: $project)\n";
run_alone;
my $some_updates = 0;
-foreach my $publish_dir (@{$config->{publish_dirs}}) {
- my $new_version = get_new_version($config, $publish_dir);
+foreach my $publish_dir (@{get_config('publish_dirs')}) {
+ my $new_version = get_new_version($publish_dir);
next unless $new_version;
- fetch_version($config, $publish_dir, $new_version);
- sign_version($config, $publish_dir, $new_version);
- update_responses($config, $publish_dir, $new_version);
+ fetch_version($publish_dir, $new_version);
+ sign_version($publish_dir, $new_version);
+ update_responses($publish_dir, $new_version);
set_current_version($publish_dir, $new_version);
- remove_oldversions($config, $publish_dir, $new_version);
+ remove_oldversions($publish_dir, $new_version);
$some_updates = 1;
}
-sync_dest($config) if $some_updates;
+sync_dest() if $some_updates;
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-bundle-testsuite][main] Bug 40073: Create incrementals for base-browser nightly builds
by boklm (@boklm) 20 Mar '23
by boklm (@boklm) 20 Mar '23
20 Mar '23
boklm pushed to branch main at The Tor Project / Applications / tor-browser-bundle-testsuite
Commits:
2190cfc8 by Nicolas Vigier at 2023-03-20T15:53:59+01:00
Bug 40073: Create incrementals for base-browser nightly builds
- - - - -
1 changed file:
- TBBTestSuite/TestSuite/TorBrowserBuild.pm
Changes:
=====================================
TBBTestSuite/TestSuite/TorBrowserBuild.pm
=====================================
@@ -194,6 +194,13 @@ sub set_tests {
],
publish_dir => 'basebrowser-nightly-linux-x86_64',
},
+ {
+ name => 'basebrowser-incrementals-nightly-linux-x86_64',
+ descr => 'create incrementals for base-browser nightly linux-x86_64',
+ type => 'make_incrementals',
+ publish_dir => 'basebrowser-nightly-linux-x86_64',
+ projectname => 'basebrowser',
+ },
{
name => 'basebrowser-nightly-linux-i686',
descr => 'build base-browser nightly linux-i686',
@@ -207,6 +214,13 @@ sub set_tests {
],
publish_dir => 'basebrowser-nightly-linux-i686',
},
+ {
+ name => 'basebrowser-incrementals-nightly-linux-i686',
+ descr => 'create incrementals for base-browser nightly linux-i686',
+ type => 'make_incrementals',
+ publish_dir => 'basebrowser-nightly-linux-i686',
+ projectname => 'basebrowser',
+ },
{
name => 'basebrowser-nightly-windows-i686',
descr => 'build base-browser nightly windows-i686',
@@ -220,6 +234,13 @@ sub set_tests {
],
publish_dir => 'basebrowser-nightly-windows-i686',
},
+ {
+ name => 'basebrowser-incrementals-nightly-windows-i686',
+ descr => 'create incrementals for base-browser nightly windows-i686',
+ type => 'make_incrementals',
+ publish_dir => 'basebrowser-nightly-windows-i686',
+ projectname => 'basebrowser',
+ },
{
name => 'basebrowser-nightly-windows-x86_64',
descr => 'build base-browser nightly windows-x86_64',
@@ -233,6 +254,13 @@ sub set_tests {
],
publish_dir => 'basebrowser-nightly-windows-x86_64',
},
+ {
+ name => 'basebrowser-incrementals-nightly-windows-x86_64',
+ descr => 'create incrementals for base-browser nightly windows-x86_64',
+ type => 'make_incrementals',
+ publish_dir => 'basebrowser-nightly-windows-x86_64',
+ projectname => 'basebrowser',
+ },
{
name => 'basebrowser-nightly-macos',
descr => 'build base-browser nightly macos',
@@ -246,6 +274,13 @@ sub set_tests {
],
publish_dir => 'basebrowser-nightly-macos',
},
+ {
+ name => 'basebrowser-incrementals-nightly-macos',
+ descr => 'create incrementals for base-browser nightly macos (universal)',
+ type => 'make_incrementals',
+ publish_dir => 'basebrowser-nightly-macos',
+ projectname => 'basebrowser',
+ },
];
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-bundle-testsuite…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-bundle-testsuite…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-102.9.0esr-12.5-1] Bug 41682: Add base-browser nightly mar signing key
by boklm (@boklm) 20 Mar '23
by boklm (@boklm) 20 Mar '23
20 Mar '23
boklm pushed to branch base-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
9c1dd51f by Nicolas Vigier at 2023-03-20T15:32:52+01:00
Bug 41682: Add base-browser nightly mar signing key
- - - - -
2 changed files:
- toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
- toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
Changes:
=====================================
toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
=====================================
Binary files a/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der and b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der differ
=====================================
toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
=====================================
Binary files a/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der and b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der differ
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/9c1dd51…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/9c1dd51…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Bug 40817: Add basebrowser-incrementals-nightly makefile target
by boklm (@boklm) 20 Mar '23
by boklm (@boklm) 20 Mar '23
20 Mar '23
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
e13d27af by Nicolas Vigier at 2023-03-20T15:47:25+01:00
Bug 40817: Add basebrowser-incrementals-nightly makefile target
- - - - -
1 changed file:
- Makefile
Changes:
=====================================
Makefile
=====================================
@@ -396,6 +396,11 @@ basebrowser-testbuild-macos-aarch64: submodule-update
basebrowser-testbuild-src: submodule-update
$(rbm) build release --target testbuild --target browser-src-testbuild --target basebrowser
+basebrowser-incrementals-nightly: submodule-update
+ $(rbm) build release --step update_responses_config --target nightly --target basebrowser
+ NO_CODESIGNATURE=1 tools/update-responses/gen_incrementals nightly
+ $(rbm) build release --step hash_incrementals --target nightly --target basebrowser
+
###########################
# Privacy Browser Targets #
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser] Pushed new tag tor-browser-102.9.0esr-12.5-1-build2
by Richard Pospesel (@richard) 20 Mar '23
by Richard Pospesel (@richard) 20 Mar '23
20 Mar '23
Richard Pospesel pushed new tag tor-browser-102.9.0esr-12.5-1-build2 at The Tor Project / Applications / Tor Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/tree/tor-brows…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-102.9.0esr-12.5-1] 4 commits: fixup! Bug 4234: Use the Firefox Update Process for Tor Browser.
by Pier Angelo Vendrame (@pierov) 20 Mar '23
by Pier Angelo Vendrame (@pierov) 20 Mar '23
20 Mar '23
Pier Angelo Vendrame pushed to branch tor-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
9eac9235 by Pier Angelo Vendrame at 2023-03-20T14:38:14+01:00
fixup! Bug 4234: Use the Firefox Update Process for Tor Browser.
Bug 41668: Port some updater patches to Base Browser
Changes that will need to remain when actually cherry-picking to
base-browser, because they contain name fixes, for example.
Or, for UpdateService.jsm, they need to stay because they make the patch
more understandable.
- - - - -
cfc8f07d by Pier Angelo Vendrame at 2023-03-20T14:38:23+01:00
fixup! Bug 4234: Use the Firefox Update Process for Tor Browser.
Bug 41668: Port some updater patches to Base Browser
Remove Tor Browser-only additions to make the updater patches usable
with Base Browser.
This commit should be reverted, and become a commit on its own, to
further adapt the updater to Tor Browser needs.
- - - - -
648dd29f by Pier Angelo Vendrame at 2023-03-20T14:38:24+01:00
Bug 41668: Tweaks to the Base Browser updater for Tor Browser
This commit was once part of "Bug 4234: Use the Firefox Update Process
for Tor Browser.".
However, some parts of it were not needed for Base Browser and some
derivative browsers.
Therefore, we extracted from that commit the parts for Tor Browser
legacy, and we add them back to the patch set with this commit.
- - - - -
e5e872d6 by Kathy Brade at 2023-03-20T14:38:24+01:00
Bug 12647: Support symlinks in the updater.
- - - - -
11 changed files:
- browser/components/BrowserContentHandler.jsm
- build/moz.configure/init.configure
- toolkit/modules/UpdateUtils.jsm
- toolkit/mozapps/extensions/AddonManager.jsm
- toolkit/mozapps/update/UpdateService.jsm
- toolkit/mozapps/update/UpdateServiceStub.jsm
- toolkit/mozapps/update/common/updatehelper.cpp
- toolkit/xre/nsAppRunner.cpp
- toolkit/xre/nsUpdateDriver.cpp
- toolkit/xre/nsXREDirProvider.cpp
- tools/update-packaging/make_incremental_update.sh
Changes:
=====================================
browser/components/BrowserContentHandler.jsm
=====================================
@@ -37,7 +37,7 @@ XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal", () =>
);
XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
-const kTBSavedVersionPref =
+const FORK_VERSION_PREF =
"browser.startup.homepage_override.torbrowser.version";
// One-time startup homepage override configurations
@@ -103,8 +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 or Tor Browser version (i.e. right
- * after an upgrade).
+ * Gecko milestone or fork 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.
@@ -121,7 +121,7 @@ function needHomepageOverride(prefb) {
var mstone = Services.appinfo.platformVersion;
- var savedTBVersion = prefb.getCharPref(kTBSavedVersionPref, null);
+ var savedForkVersion = prefb.getCharPref(FORK_VERSION_PREF, null);
var savedBuildID = prefb.getCharPref(
"browser.startup.homepage_override.buildID",
@@ -144,20 +144,13 @@ function needHomepageOverride(prefb) {
prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
- prefb.setCharPref(kTBSavedVersionPref, AppConstants.BASE_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;
+ prefb.setCharPref(FORK_VERSION_PREF, AppConstants.BASE_BROWSER_VERSION);
+ return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
}
- if (AppConstants.BASE_BROWSER_VERSION != savedTBVersion) {
+ if (AppConstants.BASE_BROWSER_VERSION != savedForkVersion) {
prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
- prefb.setCharPref(kTBSavedVersionPref, AppConstants.BASE_BROWSER_VERSION);
+ prefb.setCharPref(FORK_VERSION_PREF, AppConstants.BASE_BROWSER_VERSION);
return OVERRIDE_NEW_MSTONE;
}
@@ -689,8 +682,8 @@ nsBrowserContentHandler.prototype = {
"unknown"
);
- // We do the same for the Tor Browser version.
- let old_tbversion = prefb.getCharPref(kTBSavedVersionPref, null);
+ // We do the same for the fork version.
+ let old_forkVersion = prefb.getCharPref(FORK_VERSION_PREF, null);
override = needHomepageOverride(prefb);
if (override != OVERRIDE_NONE) {
@@ -728,7 +721,7 @@ nsBrowserContentHandler.prototype = {
"startup.homepage_override_url"
);
let update = UpdateManager.readyUpdate;
- let old_version = old_tbversion ? old_tbversion : old_mstone;
+ let old_version = old_forkVersion ? old_forkVersion : old_mstone;
if (
update &&
Services.vc.compare(update.appVersion, old_version) > 0
@@ -740,8 +733,8 @@ nsBrowserContentHandler.prototype = {
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
overridePage = overridePage.replace(
- "%OLD_TOR_BROWSER_VERSION%",
- old_tbversion
+ "%OLD_BASE_BROWSER_VERSION%",
+ old_forkVersion
);
if (overridePage && AppConstants.BASE_BROWSER_UPDATE) {
prefb.setCharPref("torbrowser.post_update.url", overridePage);
=====================================
build/moz.configure/init.configure
=====================================
@@ -954,10 +954,10 @@ def version_path(path):
return 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, we're building Release/Beta (define RELEASE_OR_BETA)
+# Firefox looks for "a" or "a1" in the milestone to detect whether nightly
+# features should be enabled. We do not want them, because we want our nightly
+# builds to be as close as possible to actual releases.
+# So we set always is_release_or_beta to True.
@depends(build_environment, build_project, version_path, "--help")
@imports(_from="__builtin__", _import="open")
@imports("os")
@@ -1001,10 +1001,8 @@ def milestone(build_env, build_project, version_path, _):
is_nightly = is_release_or_beta = is_early_beta_or_earlier = None
- if "a1" in milestone:
- is_nightly = True
- else:
- is_release_or_beta = True
+ # Do not enable extra nightly features
+ is_release_or_beta = True
major_version = milestone.split(".")[0]
m = re.search(r"([ab]\d+)", milestone)
=====================================
toolkit/modules/UpdateUtils.jsm
=====================================
@@ -167,8 +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 (except in Tor Browser), this setting is shared across all profiles
- * for the installation
+ * On Windows (except in Base Browser and derivatives), 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.
*
@@ -184,8 +184,8 @@ var UpdateUtils = {
* updates" and "Check for updates but let you choose to install them" options
* in about:preferences.
*
- * On Windows (except in Tor Browser), this setting is shared across all profiles
- * for the installation
+ * On Windows (except in Base Browser and derivatives), 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.
*
=====================================
toolkit/mozapps/extensions/AddonManager.jsm
=====================================
@@ -40,7 +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_EM_LAST_FORK_VERSION = "extensions.lastTorBrowserVersion";
const PREF_MIN_WEBEXT_PLATFORM_VERSION =
"extensions.webExtensionsMinPlatformVersion";
@@ -645,26 +645,19 @@ 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.BASE_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.
+ // To ensure that extension and plugin code gets a chance to run after
+ // each browser update, set appChanged = true when BASE_BROWSER_VERSION
+ // has changed even if the Mozilla app version has not changed.
+ const forkChanged = AppConstants.BASE_BROWSER_VERSION !==
+ Services.prefs.getCharPref(PREF_EM_LAST_FORK_VERSION, "");
+ if (forkChanged) {
+ // appChanged could be undefined (in case of a new profile)
if (appChanged === false) {
appChanged = true;
}
Services.prefs.setCharPref(
- PREF_EM_LAST_TORBROWSER_VERSION,
+ PREF_EM_LAST_FORK_VERSION,
AppConstants.BASE_BROWSER_VERSION
);
}
=====================================
toolkit/mozapps/update/UpdateService.jsm
=====================================
@@ -1587,33 +1587,32 @@ function handleUpdateFailure(update, errorCode) {
);
cancelations++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
- if (AppConstants.platform == "macosx") {
- if (AppConstants.BASE_BROWSER_UPDATE) {
- cleanupActiveUpdates();
+ if (AppConstants.platform == "macosx" && AppConstants.BASE_BROWSER_UPDATE) {
+ cleanupActiveUpdates();
+ update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
+ } else 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();
} else {
- let osxCancelations = Services.prefs.getIntPref(
- PREF_APP_UPDATE_CANCELATIONS_OSX,
- 0
- );
- osxCancelations++;
- Services.prefs.setIntPref(
- PREF_APP_UPDATE_CANCELATIONS_OSX,
- osxCancelations
+ writeStatusFile(
+ getReadyUpdateDir(),
+ (update.state = STATE_PENDING_ELEVATE)
);
- 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 {
=====================================
toolkit/mozapps/update/UpdateServiceStub.jsm
=====================================
@@ -80,8 +80,9 @@ 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.
+ // In Base Browser and derivatives, 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" &&
=====================================
toolkit/mozapps/update/common/updatehelper.cpp
=====================================
@@ -68,8 +68,8 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) {
#ifdef BASE_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
+ // updates and is therefore not called by Base Browser's updater. We stub
+ // it out to avoid any chance that the Base Browser updater will create
// files under C:\Program Files (x86)\ or a similar location.
return FALSE;
#else
=====================================
toolkit/xre/nsAppRunner.cpp
=====================================
@@ -3657,9 +3657,9 @@ static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion,
nsAutoCString buf;
- nsAutoCString tbVersion(BASE_BROWSER_VERSION_QUOTED);
+ nsAutoCString forkVersion(BASE_BROWSER_VERSION_QUOTED);
rv = parser.GetString("Compatibility", "LastTorBrowserVersion", buf);
- if (NS_FAILED(rv) || !tbVersion.Equals(buf)) return false;
+ if (NS_FAILED(rv) || !forkVersion.Equals(buf)) return false;
rv = parser.GetString("Compatibility", "LastOSABI", buf);
if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false;
@@ -3746,11 +3746,11 @@ static void WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion,
PR_Write(fd, kHeader, sizeof(kHeader) - 1);
PR_Write(fd, aVersion.get(), aVersion.Length());
- nsAutoCString tbVersion(BASE_BROWSER_VERSION_QUOTED);
- static const char kTorBrowserVersionHeader[] =
+ nsAutoCString forkVersion(BASE_BROWSER_VERSION_QUOTED);
+ static const char kForkVersionHeader[] =
NS_LINEBREAK "LastTorBrowserVersion=";
- PR_Write(fd, kTorBrowserVersionHeader, sizeof(kTorBrowserVersionHeader) - 1);
- PR_Write(fd, tbVersion.get(), tbVersion.Length());
+ PR_Write(fd, kForkVersionHeader, sizeof(kForkVersionHeader) - 1);
+ PR_Write(fd, forkVersion.get(), forkVersion.Length());
static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI=";
PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1);
=====================================
toolkit/xre/nsUpdateDriver.cpp
=====================================
@@ -87,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("nsUpdateDriver SetupMacCommandLine");
+ Monitor monitor MOZ_UNANNOTATED("nsUpdateDriver SetupMacCommandLine");
nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
"UpdateDriverSetupMacCommandLine",
=====================================
toolkit/xre/nsXREDirProvider.cpp
=====================================
@@ -1213,20 +1213,27 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
NS_ENSURE_SUCCESS(rv, rv);
#if defined(BASE_BROWSER_UPDATE)
- // For Tor Browser, we store update history, etc. within the UpdateInfo
- // directory under the user data directory.
- rv = GetTorBrowserUserDataDir(getter_AddRefs(updRoot));
+ nsCOMPtr<nsIFile> dataDir;
+ // For Base Browser and derivatives, we store update history, etc. within the
+ // UpdateInfo directory under the user data directory.
+# if defined(ANDROID)
+# error "The Base Browser updater is not supported on Android."
+# else
+ rv = GetUserDataDirectoryHome(getter_AddRefs(dataDir), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dataDir->GetParent(getter_AddRefs(updRoot));
NS_ENSURE_SUCCESS(rv, rv);
+# endif
rv = updRoot->AppendNative("UpdateInfo"_ns);
NS_ENSURE_SUCCESS(rv, rv);
+
# if defined(XP_MACOSX)
- // 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.
+ // Since the 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 our 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 appPath;
rv = appFile->GetPath(appPath);
NS_ENSURE_SUCCESS(rv, rv);
=====================================
tools/update-packaging/make_incremental_update.sh
=====================================
@@ -79,7 +79,7 @@ if [ $# = 0 ]; then
fi
# Firefox uses requested_forced_updates='Contents/MacOS/firefox Contents/Resources/defaults/pref/channel-prefs.js'
-# - 'Contents/MacOS/firefox' is required for Bugzilla 770996 but in Tor Browser we do not need that fix.
+# - 'Contents/MacOS/firefox' is required for Bugzilla 770996 but Base Browser and derivatives do not need that fix.
# - 'Contents/Resources/defaults/pref/channel-prefs.js' is required for 1804303 to avoid a failed code signing signature
# check on macOS aarch64; it is unlikely that users will run into this issue but there's no harm in including it
requested_forced_updates="Contents/Resources/defaults/pref/channel-prefs.js"
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/d41e60…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/d41e60…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Bug 40813: Enable updater for basebrowser nightly
by boklm (@boklm) 20 Mar '23
by boklm (@boklm) 20 Mar '23
20 Mar '23
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
2e2a5d9f by Nicolas Vigier at 2023-03-20T11:20:23+01:00
Bug 40813: Enable updater for basebrowser nightly
- - - - -
2 changed files:
- projects/firefox/mozconfig
- rbm.conf
Changes:
=====================================
projects/firefox/mozconfig
=====================================
@@ -66,6 +66,9 @@
[% END -%]
[% END -%]
+ac_add_options --[% IF c("var/updater_enabled") %]enable[% ELSE %]disable[% END %]-updater
+ac_add_options --[% IF c("var/updater_enabled") %]enable[% ELSE %]disable[% END %]-base-browser-update
+
mk_add_options MOZ_PARALLEL_BUILD=[% c("num_procs") %]
export MOZ_INCLUDE_SOURCE_INFO=1
=====================================
rbm.conf
=====================================
@@ -238,7 +238,7 @@ targets:
projectname: basebrowser
Project_Name: 'Base Browser'
ProjectName: BaseBrowser
- updater_enabled: 0
+ updater_enabled: '[% c("var/nightly") %]'
privacybrowser:
var:
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-update-responses][main] release: new version, 12.0.4
by Richard Pospesel (@richard) 18 Mar '23
by Richard Pospesel (@richard) 18 Mar '23
18 Mar '23
Richard Pospesel pushed to branch main at The Tor Project / Applications / Tor Browser update responses
Commits:
6fda6459 by Richard Pospesel at 2023-03-18T11:06:52+00:00
release: new version, 12.0.4
- - - - -
30 changed files:
- update_3/release/.htaccess
- − update_3/release/12.0.2-12.0.3-linux32-ALL.xml
- − update_3/release/12.0.2-12.0.3-linux64-ALL.xml
- − update_3/release/12.0.2-12.0.3-macos-ALL.xml
- − update_3/release/12.0.2-12.0.3-win32-ALL.xml
- − update_3/release/12.0.2-12.0.3-win64-ALL.xml
- + update_3/release/12.0.3-12.0.4-linux32-ALL.xml
- + update_3/release/12.0.3-12.0.4-linux64-ALL.xml
- + update_3/release/12.0.3-12.0.4-macos-ALL.xml
- + update_3/release/12.0.3-12.0.4-win32-ALL.xml
- + update_3/release/12.0.3-12.0.4-win64-ALL.xml
- − update_3/release/12.0.3-linux32-ALL.xml
- − update_3/release/12.0.3-linux64-ALL.xml
- − update_3/release/12.0.3-macos-ALL.xml
- − update_3/release/12.0.3-win32-ALL.xml
- − update_3/release/12.0.3-win64-ALL.xml
- + update_3/release/12.0.4-linux32-ALL.xml
- + update_3/release/12.0.4-linux64-ALL.xml
- + update_3/release/12.0.4-macos-ALL.xml
- + update_3/release/12.0.4-win32-ALL.xml
- + update_3/release/12.0.4-win64-ALL.xml
- update_3/release/download-android-aarch64.json
- update_3/release/download-android-armv7.json
- update_3/release/download-android-x86.json
- update_3/release/download-android-x86_64.json
- update_3/release/download-linux-i686.json
- update_3/release/download-linux-x86_64.json
- update_3/release/download-macos.json
- update_3/release/download-windows-i686.json
- update_3/release/download-windows-x86_64.json
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build] Pushed new tag tbb-12.0.4-build2
by Richard Pospesel (@richard) 17 Mar '23
by Richard Pospesel (@richard) 17 Mar '23
17 Mar '23
Richard Pospesel pushed new tag tbb-12.0.4-build2 at The Tor Project / Applications / tor-browser-build
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/tree/tbb…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-12.0] fixup! Bug 40765: Prepare stable release 12.0.4
by Richard Pospesel (@richard) 17 Mar '23
by Richard Pospesel (@richard) 17 Mar '23
17 Mar '23
Richard Pospesel pushed to branch maint-12.0 at The Tor Project / Applications / tor-browser-build
Commits:
e689b012 by Richard Pospesel at 2023-03-15T19:32:33+00:00
fixup! Bug 40765: Prepare stable release 12.0.4
- - - - -
5 changed files:
- projects/android-components/config
- projects/fenix/config
- projects/firefox/config
- projects/geckoview/config
- rbm.conf
Changes:
=====================================
projects/android-components/config
=====================================
@@ -1,7 +1,7 @@
# vim: filetype=yaml sw=2
version: '[% c("abbrev") %]'
filename: '[% project %]-[% c("version") %]-[% c("var/build_id") %].tar.gz'
-git_hash: '[% project %]-[% c("var/android_components_version") %]-[% c("var/torbrowser_branch") %]-1-build5'
+git_hash: '[% project %]-[% c("var/android_components_version") %]-[% c("var/torbrowser_branch") %]-1-build6'
git_url: https://gitlab.torproject.org/tpo/applications/android-components.git
tag_gpg_id: 1
gpg_keyring:
=====================================
projects/fenix/config
=====================================
@@ -1,7 +1,7 @@
# vim: filetype=yaml sw=2
version: '[% c("abbrev") %]'
filename: 'fenix-[% c("version") %]-[% c("var/build_id") %].tar.gz'
-git_hash: 'tor-browser-[% c("var/fenix_version") %]-[% c("var/torbrowser_branch") %]-2-build2'
+git_hash: 'tor-browser-[% c("var/fenix_version") %]-[% c("var/torbrowser_branch") %]-2-build3'
git_url: https://gitlab.torproject.org/tpo/applications/fenix.git
tag_gpg_id: 1
gpg_keyring:
=====================================
projects/firefox/config
=====================================
@@ -15,7 +15,7 @@ var:
firefox_platform_version: 102.9.0
firefox_version: '[% c("var/firefox_platform_version") %]esr'
browser_branch: '12.0-1'
- browser_build: 1
+ browser_build: 2
branding_directory: 'browser/branding/alpha'
copyright_year: '[% exec("git show -s --format=%ci").remove("-.*") %]'
nightly_updates_osname: '[% c("var/osname") %]'
@@ -65,8 +65,7 @@ steps:
targets:
basebrowser:
- # basebrowser tag always has a -build1 suffix
- git_hash: '[% c("var/project-name") %]-[% c("var/firefox_version") %]-[% c("var/browser_branch") %]-build1'
+ git_hash: '[% c("var/project-name") %]-[% c("var/firefox_version") %]-[% c("var/browser_branch") %]-build2'
release:
var:
=====================================
projects/geckoview/config
=====================================
@@ -14,7 +14,7 @@ container:
var:
geckoview_version: 102.9.0esr
torbrowser_branch: 12.0-1
- browser_build: 1
+ browser_build: 2
copyright_year: '[% exec("git show -s --format=%ci").remove("-.*") %]'
deps:
- build-essential
=====================================
rbm.conf
=====================================
@@ -72,7 +72,7 @@ buildconf:
var:
torbrowser_version: '12.0.4'
- torbrowser_build: 'build1'
+ torbrowser_build: 'build2'
torbrowser_incremental_from:
- 12.0.3
build_mar: 1
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/fenix] Pushed new tag tor-browser-102.2.1-12.0-2-build3
by Richard Pospesel (@richard) 16 Mar '23
by Richard Pospesel (@richard) 16 Mar '23
16 Mar '23
Richard Pospesel pushed new tag tor-browser-102.2.1-12.0-2-build3 at The Tor Project / Applications / fenix
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/tree/tor-browser-102…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/fenix] Pushed new tag tor-browser-102.2.1-12.5-1-build3
by Richard Pospesel (@richard) 16 Mar '23
by Richard Pospesel (@richard) 16 Mar '23
16 Mar '23
Richard Pospesel pushed new tag tor-browser-102.2.1-12.5-1-build3 at The Tor Project / Applications / fenix
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/tree/tor-browser-102…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/fenix][tor-browser-102.2.1-12.5-1] 5 commits: Bug 1812518 - Control the snackbar positioning from Fenix
by Richard Pospesel (@richard) 16 Mar '23
by Richard Pospesel (@richard) 16 Mar '23
16 Mar '23
Richard Pospesel pushed to branch tor-browser-102.2.1-12.5-1 at The Tor Project / Applications / fenix
Commits:
6c861589 by Mugurell at 2023-03-16T12:20:55+00:00
Bug 1812518 - Control the snackbar positioning from Fenix
Previously Android-Components - BrowserToolbarBehavior would be responsible
for positioning the snackbar above the toolbar.
With that responsibility removed we can handle in Fenix positioning the
snackbar depending on the toolbar and many more cases - like positioning it
depending on the download dialogs.
- - - - -
3dcc5001 by Mugurell at 2023-03-16T12:20:55+00:00
Bug 1812518 - Show the download dialog as an Android View
Tried to mimic the UX of a modal dialog while using Android Views.
This meant including a scrim that would consume all touches and theming the
navigation bar and status bar.
Avoiding a dialog and a separate window will allow the snackbar to see the
new "dialog" as a sibling in a CoordinatorLayout parent and so be able to
position itself based on the new "dialog".
This patch also added "start_download_dialog_layout" from A-C as it leads to
simpler and less code needed to style the layout - colors / shapes with
everything happening in XML versus calculating the values then setting them
programatically.
- - - - -
f66d9ac0 by Mugurell at 2023-03-16T12:20:55+00:00
Bug 1812518 - Show the 3rd party download dialog as an Android View
This uses the same direction as the before patch - inflating a new View that
can then serve as an anchor for the Snackbar.
Here we could use directly the AC layout as it needed no special customization.
- - - - -
39a422c9 by Mugurell at 2023-03-16T12:20:55+00:00
Bug 1812518 - Fix UI tests affected by the refactoring.
- - - - -
10a73978 by Mugurell at 2023-03-16T12:20:55+00:00
Bug 1812518 - Update to latest AndroidComponents
The new version will contain the support for allowing to style download dialogs.
- - - - -
20 changed files:
- app/src/androidTest/java/org/mozilla/fenix/ui/CollectionTest.kt
- app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt
- app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt
- app/src/androidTest/java/org/mozilla/fenix/ui/robots/DownloadRobot.kt
- app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
- app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
- app/src/main/java/org/mozilla/fenix/components/FenixSnackbar.kt
- + app/src/main/java/org/mozilla/fenix/components/FenixSnackbarBehavior.kt
- + app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt
- + app/src/main/res/drawable/download_dialog_download_button_background.xml
- + app/src/main/res/layout/dialog_scrim.xml
- app/src/main/res/layout/fragment_browser.xml
- + app/src/main/res/layout/start_download_dialog_layout.xml
- app/src/main/res/values/colors.xml
- app/src/main/res/values/dimens.xml
- + app/src/test/java/org/mozilla/fenix/components/FenixSnackbarBehaviorTest.kt
- + app/src/test/java/org/mozilla/fenix/downloads/FirstPartyDownloadDialogTest.kt
- + app/src/test/java/org/mozilla/fenix/downloads/StartDownloadDialogTest.kt
- + app/src/test/java/org/mozilla/fenix/downloads/ThirdPartyDownloadDialogTest.kt
- app/src/test/java/org/mozilla/fenix/tabstray/ext/FenixSnackbarKtTest.kt
Changes:
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/CollectionTest.kt
=====================================
@@ -57,8 +57,10 @@ class CollectionTest {
featureSettingsHelper.resetAllFeatureFlags()
}
- @Test
+
// open a webpage, and add currently opened tab to existing collection
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812580")
+ @Test
fun mainMenuSaveToExistingCollection() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
@@ -84,6 +86,7 @@ class CollectionTest {
}
}
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812580")
@Test
fun verifyAddTabButtonOfCollectionMenu() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt
=====================================
@@ -75,7 +75,7 @@ class DownloadFileTypesTest(fileName: String) {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
- }.closePrompt {
+ }.closeCompletedDownloadPrompt {
}.openThreeDotMenu {
}.openDownloadsManager {
waitForDownloadsListToExist()
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt
=====================================
@@ -105,7 +105,7 @@ class DownloadTest {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
- }.closePrompt { }
+ }
mDevice.openNotification()
notificationShade {
verifySystemNotificationExists("Download completed")
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/robots/DownloadRobot.kt
=====================================
@@ -12,7 +12,6 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
-import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
@@ -82,6 +81,13 @@ class DownloadRobot {
return Transition()
}
+ fun closeCompletedDownloadPrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ closeCompletedDownloadButton().click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
fun closePrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
closePromptButton().click()
@@ -177,12 +183,14 @@ private fun assertDownloadNotificationPopup() {
)
}
+private fun closeCompletedDownloadButton() =
+ onView(withId(R.id.download_dialog_close_button))
+
private fun closePromptButton() =
- onView(withContentDescription("Close"))
+ onView(withId(R.id.close_button))
private fun downloadButton() =
onView(withText("Download"))
- .inRoot(isDialog())
.check(matches(isDisplayed()))
private fun openDownloadButton() =
=====================================
app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
=====================================
@@ -108,6 +108,9 @@ import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.downloads.DynamicDownloadDialog
+import org.mozilla.fenix.downloads.FirstPartyDownloadDialog
+import org.mozilla.fenix.downloads.StartDownloadDialog
+import org.mozilla.fenix.downloads.ThirdPartyDownloadDialog
import org.mozilla.fenix.ext.accessibilityManager
import org.mozilla.fenix.ext.breadcrumb
import org.mozilla.fenix.ext.components
@@ -213,6 +216,8 @@ abstract class BaseBrowserFragment :
@VisibleForTesting
internal val onboarding by lazy { FenixOnboarding(requireContext()) }
+ private var currentStartDownloadDialog: StartDownloadDialog? = null
+
@CallSuper
override fun onCreateView(
inflater: LayoutInflater,
@@ -345,7 +350,7 @@ abstract class BaseBrowserFragment :
}
viewLifecycleOwner.lifecycleScope.allowUndo(
- binding.browserLayout,
+ binding.dynamicSnackbarContainer,
snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo),
{
@@ -424,7 +429,7 @@ abstract class BaseBrowserFragment :
feature = ContextMenuFeature(
fragmentManager = parentFragmentManager,
store = store,
- candidates = getContextMenuCandidates(context, binding.browserLayout),
+ candidates = getContextMenuCandidates(context, binding.dynamicSnackbarContainer),
engineView = binding.engineView,
useCases = context.components.useCases.contextMenuUseCases,
tabId = customTabSessionId
@@ -493,7 +498,32 @@ abstract class BaseBrowserFragment :
),
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
- }
+ },
+ customFirstPartyDownloadDialog = { filename, contentSize, positiveAction, negativeAction ->
+ FirstPartyDownloadDialog(
+ activity = requireActivity(),
+ filename = filename.value,
+ contentSize = contentSize.value,
+ positiveButtonAction = positiveAction.value,
+ negativeButtonAction = negativeAction.value,
+ ).onDismiss {
+ currentStartDownloadDialog = null
+ }.show(binding.startDownloadDialogContainer).also {
+ currentStartDownloadDialog = it
+ }
+ },
+ customThirdPartyDownloadDialog = { downloaderApps, onAppSelected, negativeActionCallback ->
+ ThirdPartyDownloadDialog(
+ activity = requireActivity(),
+ downloaderApps = downloaderApps.value,
+ onAppSelected = onAppSelected.value,
+ negativeButtonAction = negativeActionCallback.value,
+ ).onDismiss {
+ currentStartDownloadDialog = null
+ }.show(binding.startDownloadDialogContainer).also {
+ currentStartDownloadDialog = it
+ }
+ },
)
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
@@ -512,7 +542,7 @@ abstract class BaseBrowserFragment :
didFail = downloadJobStatus == DownloadState.Status.FAILED,
tryAgain = downloadFeature::tryAgain,
onCannotOpenFile = {
- showCannotOpenFileError(binding.browserLayout, context, it)
+ showCannotOpenFileError(binding.dynamicSnackbarContainer, context, it)
},
binding = binding.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight
@@ -945,7 +975,7 @@ abstract class BaseBrowserFragment :
didFail = savedDownloadState.second,
tryAgain = onTryAgain,
onCannotOpenFile = {
- showCannotOpenFileError(binding.browserLayout, context, it)
+ showCannotOpenFileError(binding.dynamicSnackbarContainer, context, it)
},
binding = binding.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight,
@@ -1036,6 +1066,7 @@ abstract class BaseBrowserFragment :
it.selectedTab
}
.collect {
+ currentStartDownloadDialog?.dismiss()
handleTabSelected(it)
}
}
@@ -1104,6 +1135,7 @@ abstract class BaseBrowserFragment :
override fun onStop() {
super.onStop()
initUIJob?.cancel()
+ currentStartDownloadDialog?.dismiss()
requireComponents.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
?.let { session ->
@@ -1119,6 +1151,10 @@ abstract class BaseBrowserFragment :
return findInPageIntegration.onBackPressed() ||
fullScreenFeature.onBackPressed() ||
promptsFeature.onBackPressed() ||
+ currentStartDownloadDialog?.let {
+ it.dismiss()
+ true
+ } ?: false ||
sessionFeature.onBackPressed() ||
removeSessionIfNeeded()
}
@@ -1281,7 +1317,7 @@ abstract class BaseBrowserFragment :
withContext(Main) {
view?.let {
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = FenixSnackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
@@ -1303,7 +1339,7 @@ abstract class BaseBrowserFragment :
view?.let {
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = FenixSnackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
@@ -1346,7 +1382,7 @@ abstract class BaseBrowserFragment :
// Close find in page bar if opened
findInPageIntegration.onBackPressed()
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = false
)
@@ -1420,12 +1456,12 @@ abstract class BaseBrowserFragment :
}
private fun showCannotOpenFileError(
- view: View,
+ container: ViewGroup,
context: Context,
downloadState: DownloadState
) {
FenixSnackbar.make(
- view = view,
+ view = container,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
).setText(DynamicDownloadDialog.getCannotOpenFileErrorMessage(context, downloadState))
=====================================
app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
=====================================
@@ -347,7 +347,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
}
}
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
=====================================
app/src/main/java/org/mozilla/fenix/components/FenixSnackbar.kt
=====================================
@@ -141,10 +141,20 @@ class FenixSnackbar private constructor(
0
}
)
+
+ if (parent.id == R.id.dynamicSnackbarContainer) {
+ (parent.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
+ behavior = FenixSnackbarBehavior<FrameLayout>(
+ context = view.context,
+ toolbarPosition = view.context.settings().toolbarPosition,
+ )
+ }
+ }
}
}
// Use the same implementation of `Snackbar`
+ @Suppress("ReturnCount")
private fun findSuitableParent(_view: View?): ViewGroup? {
var view = _view
var fallback: ViewGroup? = null
@@ -159,6 +169,10 @@ class FenixSnackbar private constructor(
return view
}
+ if (view.id == R.id.dynamicSnackbarContainer) {
+ return view
+ }
+
fallback = view
}
=====================================
app/src/main/java/org/mozilla/fenix/components/FenixSnackbarBehavior.kt
=====================================
@@ -0,0 +1,77 @@
+/* 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/. */
+
+package org.mozilla.fenix.components
+
+import android.content.Context
+import android.view.Gravity
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.toolbar.ToolbarPosition
+
+/**
+ * [CoordinatorLayout.Behavior] to be used by a snackbar that want to ensure it it always positioned
+ * such that it will be shown on top (vertically) of other siblings that may obstruct it's view.
+ *
+ * @param context [Context] used for various system interactions.
+ * @param toolbarPosition Where the toolbar is positioned on the screen.
+ * Depending on it's position (top / bottom) the snackbar will be shown below / above the toolbar.
+ */
+class FenixSnackbarBehavior<V : View>(
+ context: Context,
+ @get:VisibleForTesting internal val toolbarPosition: ToolbarPosition,
+) : CoordinatorLayout.Behavior<V>(context, null) {
+
+ private val dependenciesIds = listOf(
+ R.id.startDownloadDialogContainer,
+ R.id.viewDynamicDownloadDialog,
+ R.id.toolbar,
+ )
+
+ private var currentAnchorId: Int? = View.NO_ID
+
+ override fun layoutDependsOn(
+ parent: CoordinatorLayout,
+ child: V,
+ dependency: View,
+ ): Boolean {
+ val anchorId = dependenciesIds
+ .intersect(parent.children.filter { it.isVisible }.map { it.id }.toSet())
+ .firstOrNull()
+
+ // It is possible that previous anchor's visibility is changed.
+ // The layout is updated and layoutDependsOn is called but onDependentViewChanged not.
+ // We have to check here if a new anchor is available and reparent the snackbar.
+ // This check also ensures we are not positioning the snackbar multiple times for the same anchor.
+ return if (anchorId != currentAnchorId) {
+ positionSnackbar(child, parent.children.firstOrNull { it.id == anchorId })
+ true
+ } else {
+ false
+ }
+ }
+
+ private fun positionSnackbar(snackbar: View, dependency: View?) {
+ currentAnchorId = dependency?.id ?: View.NO_ID
+ val params = snackbar.layoutParams as CoordinatorLayout.LayoutParams
+
+ if (dependency == null || (dependency.id == R.id.toolbar && toolbarPosition == ToolbarPosition.TOP)) {
+ // Position the snackbar at the bottom of the screen.
+ params.anchorId = View.NO_ID
+ params.anchorGravity = Gravity.NO_GRAVITY
+ params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ } else {
+ // Position the snackbar just above the anchor.
+ params.anchorId = dependency.id
+ params.anchorGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ }
+
+ snackbar.layoutParams = params
+ }
+}
=====================================
app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt
=====================================
@@ -0,0 +1,248 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.app.Dialog
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.view.Window
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.annotation.VisibleForTesting
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.children
+import androidx.viewbinding.ViewBinding
+import mozilla.components.feature.downloads.databinding.MozacDownloaderChooserPromptBinding
+import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
+import mozilla.components.feature.downloads.ui.DownloaderApp
+import mozilla.components.feature.downloads.ui.DownloaderAppAdapter
+import mozilla.components.support.ktx.android.view.setNavigationBarTheme
+import mozilla.components.support.ktx.android.view.setStatusBarTheme
+import org.mozilla.fenix.R
+import org.mozilla.fenix.databinding.DialogScrimBinding
+import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
+import org.mozilla.fenix.ext.settings
+
+/**
+ * Parent of all download views that can mimic a modal [Dialog].
+ *
+ * @param activity The [Activity] in which the dialog will be shown.
+ * Used to update the activity [Window] to best mimic a modal dialog.
+ */
+abstract class StartDownloadDialog(
+ private val activity: Activity,
+) {
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ internal var binding: ViewBinding? = null
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ internal var container: ViewGroup? = null
+ private var scrim: DialogScrimBinding? = null
+
+ @VisibleForTesting
+ internal var onDismiss: () -> Unit = {}
+
+ @VisibleForTesting
+ internal var initialNavigationBarColor = activity.window.navigationBarColor
+
+ @VisibleForTesting
+ internal var initialStatusBarColor = activity.window.statusBarColor
+
+ /**
+ * Show the download view.
+ *
+ * @param container The [ViewGroup] in which the download view will be inflated.
+ */
+ fun show(container: ViewGroup): StartDownloadDialog {
+ this.container = container
+
+ val dialogParent = container.parent as? ViewGroup
+ dialogParent?.let {
+ scrim = DialogScrimBinding.inflate(LayoutInflater.from(activity), dialogParent, true).apply {
+ this.scrim.setOnClickListener {
+ // Empty listener needed to prevent clicking through.
+ }
+ }
+ }
+
+ setupView()
+
+ if (activity.settings().accessibilityServicesEnabled) {
+ disableSiblingsAccessibility(dialogParent)
+ }
+
+ container.apply {
+ val params = layoutParams as CoordinatorLayout.LayoutParams
+ params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ layoutParams = params
+
+ // Set a higher elevation than the toolbar sibling which we should cover.
+ elevation = activity.resources.getDimension(R.dimen.browser_fragment_download_dialog_elevation)
+ visibility = View.VISIBLE
+ }
+
+ activity.window.setNavigationBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+ activity.window.setStatusBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+
+ return this
+ }
+
+ /**
+ * Set a callback for when the download view is dismissed.
+ *
+ * @param callback The callback for when the view is dismissed.
+ */
+ fun onDismiss(callback: () -> Unit): StartDownloadDialog {
+ this.onDismiss = callback
+ return this
+ }
+
+ /**
+ * Immediately dismiss the current download view if it is shown.
+ * This will restore the previous UI removing any other layout / window customizations.
+ */
+ fun dismiss() {
+ scrim?.let {
+ (it.root.parent as? ViewGroup)?.removeView(it.root)
+ }
+ binding?.let {
+ (it.root.parent as? ViewGroup)?.removeView(it.root)
+ }
+ enableSiblingsAccessibility(container?.parent as? ViewGroup)
+
+ container?.visibility = View.GONE
+
+ activity.window.setNavigationBarTheme(initialNavigationBarColor)
+ activity.window.setStatusBarTheme(initialStatusBarColor)
+
+ onDismiss()
+ }
+
+ @VisibleForTesting
+ internal fun enableSiblingsAccessibility(parent: ViewGroup?) {
+ parent?.children
+ ?.filterNot { it.id == R.id.startDownloadDialogContainer }
+ ?.forEach {
+ ViewCompat.setImportantForAccessibility(
+ it,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
+ )
+ }
+ }
+
+ @VisibleForTesting
+ internal fun disableSiblingsAccessibility(parent: ViewGroup?) {
+ parent?.children
+ ?.filterNot { it.id == R.id.startDownloadDialogContainer }
+ ?.forEach {
+ ViewCompat.setImportantForAccessibility(
+ it,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+ )
+ }
+ }
+
+ /**
+ * Bind all download data to the download view.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ internal abstract fun setupView()
+}
+
+/**
+ * A download view mimicking a modal dialog that allows the user to download a file with the current application.
+ *
+ * @param activity The [Activity] in which the dialog will be shown.
+ * Used to update the activity [Window] to best mimic a modal dialog.
+ * @param filename Name of the file to be downloaded. It wil be shown without any modification.
+ * @param contentSize Size of the file to be downloaded expressed as a number of bytes.
+ * It will automatically be parsed to the appropriate kilobyte or megabyte value before being shown.
+ * @param positiveButtonAction Callback for when the user interacts with the dialog to start the download.
+ * @param negativeButtonAction Callback for when the user interacts with the dialog to dismiss it.
+ */
+class FirstPartyDownloadDialog(
+ private val activity: Activity,
+ private val filename: String,
+ private val contentSize: Long,
+ private val positiveButtonAction: () -> Unit,
+ private val negativeButtonAction: () -> Unit,
+) : StartDownloadDialog(activity) {
+ override fun setupView() {
+ val dialog = StartDownloadDialogLayoutBinding.inflate(LayoutInflater.from(activity), container, true)
+ .also { binding = it }
+
+ if (contentSize > 0L) {
+ val contentSize = contentSize.toMegabyteOrKilobyteString()
+ dialog.title.text =
+ activity.getString(R.string.mozac_feature_downloads_dialog_title2, contentSize)
+ }
+
+ dialog.filename.text = filename
+
+ dialog.downloadButton.setOnClickListener {
+ positiveButtonAction()
+ dismiss()
+ }
+
+ dialog.closeButton.setOnClickListener {
+ negativeButtonAction()
+ dismiss()
+ }
+
+ if (activity.settings().accessibilityServicesEnabled) {
+ // Ensure the title of the dialog is focused and read by talkback first.
+ dialog.root.viewTreeObserver.addOnGlobalLayoutListener(
+ object : OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ dialog.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ dialog.title.run {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)
+ performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
+ }
+ }
+ },
+ )
+ }
+ }
+}
+
+/**
+ * A download view mimicking a modal dialog that presents the user with a list of all apps
+ * that can handle the download request.
+ *
+ * @param activity The [Activity] in which the dialog will be shown.
+ * Used to update the activity [Window] to best mimic a modal dialog.
+ * @param downloaderApps List of all applications that can handle the download request.
+ * @param onAppSelected Callback for when the user chooses a specific application to handle the download request.
+ * @param negativeButtonAction Callback for when the user interacts with the dialog to dismiss it.
+ */
+class ThirdPartyDownloadDialog(
+ private val activity: Activity,
+ private val downloaderApps: List<DownloaderApp>,
+ private val onAppSelected: (DownloaderApp) -> Unit,
+ private val negativeButtonAction: () -> Unit,
+) : StartDownloadDialog(activity) {
+ override fun setupView() {
+ val dialog = MozacDownloaderChooserPromptBinding.inflate(LayoutInflater.from(activity), container, true)
+ .also { binding = it }
+
+ val recyclerView = dialog.appsList
+ recyclerView.adapter = DownloaderAppAdapter(activity, downloaderApps) { app ->
+ onAppSelected(app)
+ dismiss()
+ }
+
+ dialog.closeButton.setOnClickListener {
+ negativeButtonAction()
+ dismiss()
+ }
+ }
+}
=====================================
app/src/main/res/drawable/download_dialog_download_button_background.xml
=====================================
@@ -0,0 +1,9 @@
+<?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/. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/bottom_sheet_corner_radius"/>
+ <solid android:color="?attr/accent" />
+</shape>
=====================================
app/src/main/res/layout/dialog_scrim.xml
=====================================
@@ -0,0 +1,15 @@
+<?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/. -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/material_scrim_color"
+ android:clipToPadding="false"
+ android:fitsSystemWindows="true"
+ android:importantForAccessibility="no"
+ android:soundEffectsEnabled="false" />
=====================================
app/src/main/res/layout/fragment_browser.xml
=====================================
@@ -66,6 +66,20 @@
android:layout_height="match_parent"
android:visibility="gone" />
+ <FrameLayout
+ android:id="@+id/startDownloadDialogContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:visibility="gone"
+ android:elevation="@dimen/browser_fragment_toolbar_elevation"/>
+
+ <FrameLayout
+ android:id="@+id/dynamicSnackbarContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="@dimen/browser_fragment_toolbar_elevation"/>
+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<mozilla.components.feature.prompts.creditcard.CreditCardSelectBar
=====================================
app/src/main/res/layout/start_download_dialog_layout.xml
=====================================
@@ -0,0 +1,96 @@
+<?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/. -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/dialogLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:windowBackground"
+ android:orientation="vertical"
+ app:layout_constraintBottom_toBottomOf="parent">
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_alignParentTop="true"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:importantForAccessibility="no"
+ android:scaleType="center"
+ app:srcCompat="@drawable/mozac_feature_download_ic_download"
+ app:tint="?android:attr/textColorPrimary" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBaseline="@id/icon"
+ android:layout_alignParentTop="true"
+ android:layout_marginStart="3dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="11dp"
+ android:layout_toStartOf="@id/close_button"
+ android:layout_toEndOf="@id/icon"
+ android:paddingStart="5dp"
+ android:paddingTop="4dp"
+ android:paddingEnd="5dp"
+ android:text="@string/mozac_feature_downloads_dialog_download"
+ android:textColor="?android:attr/textColorPrimary"
+ tools:text="Download (85.7 MB)"
+ tools:textColor="#000000" />
+
+ <androidx.appcompat.widget.AppCompatImageButton
+ android:id="@+id/close_button"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignBaseline="@id/icon"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginStart="3dp"
+ android:scaleType="centerInside"
+ android:background="@null"
+ android:contentDescription="@string/mozac_feature_downloads_button_close"
+ app:srcCompat="@drawable/mozac_ic_close"
+ app:tint="?android:attr/textColorPrimary"
+ tools:textColor="#000000" />
+
+ <TextView
+ android:id="@+id/filename"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignBaseline="@id/icon"
+ android:layout_marginStart="3dp"
+ android:layout_marginTop="16dp"
+ android:layout_toEndOf="@id/icon"
+ android:paddingStart="5dp"
+ android:paddingTop="4dp"
+ android:paddingEnd="5dp"
+ android:textColor="?android:attr/textColorPrimary"
+ tools:text="@tools:sample/lorem/random"
+ tools:textColor="#000000" />
+
+ <Button
+ android:id="@+id/download_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/filename"
+ android:layout_alignParentEnd="true"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="16dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:text="@string/mozac_feature_downloads_dialog_download"
+ android:background="@drawable/download_dialog_download_button_background"
+ android:textColor="?attr/textOnColorPrimary"
+ android:textAllCaps="false"
+ tools:ignore="ButtonStyleXmlDetector" />
+</RelativeLayout>
=====================================
app/src/main/res/values/colors.xml
=====================================
@@ -337,4 +337,7 @@
<!-- App Spinners colors -->
<color name="spinner_selected_item">#1415141A</color>
+
+ <!-- Material Design colors -->
+ <color name="material_scrim_color">#52000000</color>
</resources>
=====================================
app/src/main/res/values/dimens.xml
=====================================
@@ -82,6 +82,8 @@
<!--The size of the gap between the tab preview and content layout.-->
<dimen name="browser_fragment_gesture_preview_offset">48dp</dimen>
<dimen name="browser_fragment_toolbar_elevation">16dp</dimen>
+ <!-- The download dialogs are shown above the toolbar so they need a bigger elevation. -->
+ <dimen name="browser_fragment_download_dialog_elevation">17dp</dimen>
<!-- Search Fragment -->
<dimen name="search_fragment_clipboard_item_height">56dp</dimen>
=====================================
app/src/test/java/org/mozilla/fenix/components/FenixSnackbarBehaviorTest.kt
=====================================
@@ -0,0 +1,251 @@
+/* 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/. */
+
+package org.mozilla.fenix.components
+
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.toolbar.ToolbarPosition
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+
+@RunWith(FenixRobolectricTestRunner::class)
+class FenixSnackbarBehaviorTest {
+ private val snackbarParams = CoordinatorLayout.LayoutParams(0, 0)
+ private val snackbarContainer = FrameLayout(testContext)
+ private val dependency = View(testContext)
+ private val parent = CoordinatorLayout(testContext)
+
+ @Before
+ fun setup() {
+ snackbarContainer.layoutParams = snackbarParams
+ parent.addView(dependency)
+ }
+
+ @Test
+ fun `GIVEN no valid anchors are shown WHEN the snackbar is shown THEN don't anchor it`() {
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ @Test
+ fun `GIVEN the dynamic download dialog is shown WHEN the snackbar is shown THEN place the snackbar above the dialog`() {
+ dependency.id = R.id.viewDynamicDownloadDialog
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor()
+ }
+
+ @Test
+ fun `GIVEN a bottom toolbar is shown WHEN the snackbar is shown THEN place the snackbar above the toolbar`() {
+ dependency.id = R.id.toolbar
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor()
+ }
+
+ @Test
+ fun `GIVEN a toolbar and a dynamic download dialog are shown WHEN the snackbar is shown THEN place the snackbar above the dialog`() {
+ listOf(R.id.viewDynamicDownloadDialog, R.id.toolbar).forEach {
+ parent.addView(View(testContext).apply { id = it })
+ }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor(parent.findViewById(R.id.viewDynamicDownloadDialog))
+ }
+
+ @Test
+ fun `GIVEN a toolbar, a download dialog and a dynamic download dialog are shown WHEN the snackbar is shown THEN place the snackbar above the download dialog`() {
+ listOf(R.id.viewDynamicDownloadDialog, R.id.toolbar, R.id.startDownloadDialogContainer).forEach {
+ parent.addView(View(testContext).apply { id = it })
+ }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor(parent.findViewById(R.id.startDownloadDialogContainer))
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to the dynamic download dialog and a bottom toolbar is shown WHEN the dialog is not shown anymore THEN place the snackbar above the toolbar`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.viewDynamicDownloadDialog }
+ .also { parent.addView(it) }
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to a download dialog and another dynamic dialog is shown WHEN the dialog is not shown anymore THEN place the snackbar above the dynamic dialog`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.startDownloadDialogContainer }
+ .also { parent.addView(it) }
+ val dynamicDialog = View(testContext)
+ .apply { id = R.id.viewDynamicDownloadDialog }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dynamicDialog)
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dynamicDialog)
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to a download dialog and a bottom toolbar is shown WHEN the dialog is not shown anymore THEN place the snackbar above the toolbar`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.startDownloadDialogContainer }
+ .also { parent.addView(it) }
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to the bottom toolbar WHEN the toolbar is not shown anymore THEN place the snackbar at the bottom`() {
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the toolbar is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ toolbar.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+
+ // Test the scenario where the toolbar is removed from parent.
+ toolbar.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ parent.removeView(toolbar)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to the dynamic download dialog and a top toolbar is shown WHEN the dialog is not shown anymore THEN place the snackbar to the bottom`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.viewDynamicDownloadDialog }
+ .also { parent.addView(it) }
+ View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.TOP)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored based on a top toolbar WHEN the toolbar is not shown anymore THEN place the snackbar at the bottom`() {
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.TOP)
+
+ // Test the scenario where the toolbar is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ toolbar.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+
+ // Test the scenario where the toolbar is removed from parent.
+ toolbar.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ parent.removeView(toolbar)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ private fun assertSnackbarPlacementAboveAnchor(anchor: View = dependency) {
+ assertEquals(anchor.id, snackbarContainer.params.anchorId)
+ assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, snackbarContainer.params.anchorGravity)
+ assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, snackbarContainer.params.gravity)
+ }
+
+ private fun assertSnackbarIsPlacedAtTheBottomOfTheScreen() {
+ assertEquals(View.NO_ID, snackbarContainer.params.anchorId)
+ assertEquals(Gravity.NO_GRAVITY, snackbarContainer.params.anchorGravity)
+ assertEquals(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL, snackbarContainer.params.gravity)
+ }
+
+ private val FrameLayout.params
+ get() = layoutParams as CoordinatorLayout.LayoutParams
+}
=====================================
app/src/test/java/org/mozilla/fenix/downloads/FirstPartyDownloadDialogTest.kt
=====================================
@@ -0,0 +1,115 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.widget.FrameLayout
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.spyk
+import io.mockk.verify
+import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.robolectric.Robolectric
+
+@RunWith(FenixRobolectricTestRunner::class)
+class FirstPartyDownloadDialogTest {
+ private val activity: Activity = Robolectric.buildActivity(Activity::class.java).create().get()
+
+ @Before
+ fun setup() {
+ every { activity.settings().accessibilityServicesEnabled } returns false
+ }
+
+ @Test
+ fun `GIVEN the size of the download is known WHEN setting it's View THEN bind all provided download data and show the download size`() {
+ var wasPositiveActionDone = false
+ var wasNegativeActionDone = false
+ val contentSize = 5566L
+ val dialog = spyk(
+ FirstPartyDownloadDialog(
+ activity = activity,
+ filename = "Test",
+ contentSize = contentSize,
+ positiveButtonAction = { wasPositiveActionDone = true },
+ negativeButtonAction = { wasNegativeActionDone = true },
+ ),
+ )
+ every { dialog.dismiss() } just Runs
+ val dialogParent = FrameLayout(testContext)
+ dialog.container = dialogParent
+
+ dialog.setupView()
+
+ assertEquals(1, dialogParent.childCount)
+ assertEquals(R.id.dialogLayout, dialogParent.getChildAt(0).id)
+ val dialogBinding = dialog.binding as StartDownloadDialogLayoutBinding
+ assertEquals(
+ testContext.getString(
+ R.string.mozac_feature_downloads_dialog_title2,
+ contentSize.toMegabyteOrKilobyteString(),
+ ),
+ dialogBinding.title.text,
+ )
+ assertEquals("Test", dialogBinding.filename.text)
+ assertFalse(wasPositiveActionDone)
+ assertFalse(wasNegativeActionDone)
+ dialogBinding.downloadButton.callOnClick()
+ verify { dialog.dismiss() }
+ assertTrue(wasPositiveActionDone)
+ dialogBinding.closeButton.callOnClick()
+ verify(exactly = 2) { dialog.dismiss() }
+ assertTrue(wasNegativeActionDone)
+ }
+
+ @Test
+ fun `GIVEN the size of the download is not known WHEN setting it's View THEN bind all provided download data and show the download size`() {
+ var wasPositiveActionDone = false
+ var wasNegativeActionDone = false
+ val contentSize = 0L
+ val dialog = spyk(
+ FirstPartyDownloadDialog(
+ activity = activity,
+ filename = "Test",
+ contentSize = contentSize,
+ positiveButtonAction = { wasPositiveActionDone = true },
+ negativeButtonAction = { wasNegativeActionDone = true },
+ ),
+ )
+ every { dialog.dismiss() } just Runs
+ val dialogParent = FrameLayout(testContext)
+ dialog.container = dialogParent
+
+ dialog.setupView()
+
+ assertEquals(1, dialogParent.childCount)
+ assertEquals(R.id.dialogLayout, dialogParent.getChildAt(0).id)
+ val dialogBinding = dialog.binding as StartDownloadDialogLayoutBinding
+ assertEquals(
+ testContext.getString(R.string.mozac_feature_downloads_dialog_download),
+ dialogBinding.title.text,
+ )
+ assertEquals("Test", dialogBinding.filename.text)
+ assertFalse(wasPositiveActionDone)
+ assertFalse(wasNegativeActionDone)
+ dialogBinding.downloadButton.callOnClick()
+ verify { dialog.dismiss() }
+ assertTrue(wasPositiveActionDone)
+ dialogBinding.closeButton.callOnClick()
+ verify(exactly = 2) { dialog.dismiss() }
+ assertTrue(wasNegativeActionDone)
+ }
+}
=====================================
app/src/test/java/org/mozilla/fenix/downloads/StartDownloadDialogTest.kt
=====================================
@@ -0,0 +1,242 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Color
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.verify
+import mozilla.components.support.ktx.android.view.setNavigationBarTheme
+import mozilla.components.support.ktx.android.view.setStatusBarTheme
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.mozilla.fenix.utils.Settings
+import org.robolectric.Robolectric
+
+@RunWith(FenixRobolectricTestRunner::class)
+class StartDownloadDialogTest {
+ @Test
+ fun `WHEN the dialog is instantiated THEN cache the navigation and status bar colors`() {
+ val navigationBarColor = Color.RED
+ val statusBarColor = Color.BLUE
+ val activity: Activity = mockk {
+ every { window.navigationBarColor } returns navigationBarColor
+ every { window.statusBarColor } returns statusBarColor
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ assertEquals(navigationBarColor, dialog.initialNavigationBarColor)
+ assertEquals(statusBarColor, dialog.initialStatusBarColor)
+ }
+
+ @Test
+ fun `WHEN the view is to be shown THEN set the scrim and other window customization bind the download values`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ mockkStatic("mozilla.components.support.ktx.android.view.WindowKt", "org.mozilla.fenix.ext.ContextKt") {
+ every { any<Context>().settings() } returns mockk(relaxed = true)
+ val fluentDialog = dialog.show(dialogContainer)
+
+ val scrim = dialogParent.children.first { it.id == R.id.scrim }
+ assertTrue(scrim.hasOnClickListeners())
+ assertFalse(scrim.isSoundEffectsEnabled)
+ assertTrue(dialog.wasDownloadDataBinded)
+ assertEquals(
+ Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL,
+ (dialogContainer.layoutParams as CoordinatorLayout.LayoutParams).gravity,
+ )
+ assertEquals(
+ testContext.resources.getDimension(R.dimen.browser_fragment_download_dialog_elevation),
+ dialogContainer.elevation,
+ )
+ assertTrue(dialogContainer.isVisible)
+ verify {
+ activity.window.setNavigationBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+ activity.window.setStatusBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+ }
+ assertEquals(dialog, fluentDialog)
+ }
+ }
+
+ @Test
+ fun `GIVEN a dismiss callback WHEN the dialog is dismissed THEN the callback is informed`() {
+ var wasDismissCalled = false
+ val dialog = TestDownloadDialog(mockk(relaxed = true))
+
+ val fluentDialog = dialog.onDismiss { wasDismissCalled = true }
+ dialog.onDismiss()
+
+ assertTrue(wasDismissCalled)
+ assertEquals(dialog, fluentDialog)
+ }
+
+ @Test
+ fun `GIVEN the download dialog is shown WHEN dismissed THEN remove the scrim, the dialog and any window customizations`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ }
+ val dialog = TestDownloadDialog(activity)
+ mockkStatic("mozilla.components.support.ktx.android.view.WindowKt", "org.mozilla.fenix.ext.ContextKt") {
+ every { any<Context>().settings() } returns mockk(relaxed = true)
+ dialog.show(dialogContainer)
+ dialog.binding = StartDownloadDialogLayoutBinding
+ .inflate(LayoutInflater.from(activity), dialogContainer, true)
+
+ dialog.dismiss()
+
+ assertNull(dialogParent.children.firstOrNull { it.id == R.id.scrim })
+ assertTrue(dialogParent.childCount == 1)
+ assertTrue(dialogContainer.childCount == 0)
+ assertFalse(dialogContainer.isVisible)
+ verify {
+ activity.window.setNavigationBarTheme(dialog.initialNavigationBarColor)
+ activity.window.setStatusBarTheme(dialog.initialStatusBarColor)
+ }
+ }
+ }
+
+ @Test
+ fun `GIVEN a ViewGroup WHEN enabling accessibility THEN enable it for all children but the dialog container`() {
+ val activity: Activity = mockk(relaxed = true)
+ val dialogParent = FrameLayout(testContext)
+ FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
+ val otherView = View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ dialog.enableSiblingsAccessibility(dialogParent)
+
+ assertEquals(listOf(otherView), dialogParent.children.filter { it.isImportantForAccessibility }.toList())
+ }
+
+ @Test
+ fun `GIVEN a ViewGroup WHEN disabling accessibility THEN disable it for all children but the dialog container`() {
+ val activity: Activity = mockk(relaxed = true)
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ dialog.disableSiblingsAccessibility(dialogParent)
+
+ assertEquals(listOf(dialogContainer), dialogParent.children.filter { it.isImportantForAccessibility }.toList())
+ }
+
+ @Test
+ fun `GIVEN accessibility services are enabled WHEN the dialog is shown THEN disable siblings accessibility`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+
+ mockkStatic("org.mozilla.fenix.ext.ContextKt") {
+ val dialog = TestDownloadDialog(activity)
+
+ val settings: Settings = mockk {
+ every { accessibilityServicesEnabled } returns false
+ }
+ every { any<Context>().settings() } returns settings
+ dialog.show(dialogContainer)
+ assertEquals(2, dialogParent.children.count { it.isImportantForAccessibility })
+
+ every { settings.accessibilityServicesEnabled } returns true
+ dialog.show(dialogContainer)
+ assertEquals(listOf(dialogContainer), dialogParent.children.filter { it.isImportantForAccessibility }.toList())
+ }
+ }
+
+ @Test
+ fun `WHEN the dialog is dismissed THEN re-enable siblings accessibility`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ val accessibleView = View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ mockkStatic("org.mozilla.fenix.ext.ContextKt") {
+ val settings: Settings = mockk {
+ every { accessibilityServicesEnabled } returns true
+ }
+ every { any<Context>().settings() } returns settings
+ val dialog = TestDownloadDialog(activity)
+ dialog.show(dialogContainer)
+ dialog.binding = StartDownloadDialogLayoutBinding
+ .inflate(LayoutInflater.from(activity), dialogContainer, true)
+
+ dialog.dismiss()
+
+ assertEquals(
+ listOf(accessibleView),
+ dialogParent.children.filter { it.isVisible && it.isImportantForAccessibility }.toList(),
+ )
+ }
+ }
+}
+
+private class TestDownloadDialog(
+ activity: Activity,
+) : StartDownloadDialog(activity) {
+ var wasDownloadDataBinded = false
+
+ override fun setupView() {
+ wasDownloadDataBinded = true
+ }
+}
=====================================
app/src/test/java/org/mozilla/fenix/downloads/ThirdPartyDownloadDialogTest.kt
=====================================
@@ -0,0 +1,54 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.widget.FrameLayout
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.spyk
+import io.mockk.verify
+import mozilla.components.feature.downloads.databinding.MozacDownloaderChooserPromptBinding
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.robolectric.Robolectric
+
+@RunWith(FenixRobolectricTestRunner::class)
+class ThirdPartyDownloadDialogTest {
+ private val activity: Activity = Robolectric.buildActivity(Activity::class.java).create().get()
+
+ @Test
+ fun `GIVEN a list of downloader apps WHEN setting it's View THEN bind all provided download data`() {
+ var wasNegativeActionDone = false
+ val dialog = spyk(
+ ThirdPartyDownloadDialog(
+ activity = activity,
+ downloaderApps = listOf(mockk(), mockk()),
+ onAppSelected = { /* cannot test the viewholder click */ },
+ negativeButtonAction = { wasNegativeActionDone = true },
+ ),
+ )
+ every { dialog.dismiss() } just Runs
+ val dialogParent = FrameLayout(testContext)
+ dialog.container = dialogParent
+
+ dialog.setupView()
+
+ assertEquals(1, dialogParent.childCount)
+ assertEquals(R.id.relativeLayout, dialogParent.getChildAt(0).id)
+ val dialogBinding = dialog.binding as MozacDownloaderChooserPromptBinding
+ assertEquals(2, dialogBinding.appsList.adapter?.itemCount)
+ dialogBinding.closeButton.callOnClick()
+ assertTrue(wasNegativeActionDone)
+ verify { dialog.dismiss() }
+ }
+}
=====================================
app/src/test/java/org/mozilla/fenix/tabstray/ext/FenixSnackbarKtTest.kt
=====================================
@@ -6,16 +6,29 @@ package org.mozilla.fenix.tabstray.ext
import android.content.Context
import android.view.View
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
import io.mockk.every
import io.mockk.mockk
+import io.mockk.mockkStatic
import io.mockk.verifyOrder
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
+import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
+import org.mozilla.fenix.components.FenixSnackbarBehavior
+import org.mozilla.fenix.components.toolbar.ToolbarPosition
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.helpers.MockkRetryTestRule
import org.mozilla.fenix.tabstray.TabsTrayFragment.Companion.ELEVATION
+import org.mozilla.fenix.utils.Settings
+@RunWith(FenixRobolectricTestRunner::class)
class FenixSnackbarKtTest {
@get:Rule
@@ -94,4 +107,24 @@ class FenixSnackbarKtTest {
snackbar.setAction("test1", any())
}
}
+
+ @Test
+ fun `GIVEN the snackbar is a child of dynamic container WHEN it is shown THEN enable the dynamic behavior`() {
+ val container = FrameLayout(testContext).apply {
+ id = R.id.dynamicSnackbarContainer
+ layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ }
+ val settings: Settings = mockk(relaxed = true) {
+ every { toolbarPosition } returns ToolbarPosition.BOTTOM
+ }
+ mockkStatic("org.mozilla.fenix.ext.ContextKt") {
+ every { any<Context>().settings() } returns settings
+
+ FenixSnackbar.make(view = container)
+
+ val behavior = (container.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior
+ assertTrue(behavior is FenixSnackbarBehavior)
+ assertEquals(ToolbarPosition.BOTTOM, (behavior as? FenixSnackbarBehavior)?.toolbarPosition)
+ }
+ }
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/compare/667092ef6cae…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/compare/667092ef6cae…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/fenix][tor-browser-102.2.1-12.0-2] 5 commits: Bug 1812518 - Control the snackbar positioning from Fenix
by Richard Pospesel (@richard) 16 Mar '23
by Richard Pospesel (@richard) 16 Mar '23
16 Mar '23
Richard Pospesel pushed to branch tor-browser-102.2.1-12.0-2 at The Tor Project / Applications / fenix
Commits:
6b5be770 by Mugurell at 2023-03-15T18:43:49+00:00
Bug 1812518 - Control the snackbar positioning from Fenix
Previously Android-Components - BrowserToolbarBehavior would be responsible
for positioning the snackbar above the toolbar.
With that responsibility removed we can handle in Fenix positioning the
snackbar depending on the toolbar and many more cases - like positioning it
depending on the download dialogs.
- - - - -
09278aff by Mugurell at 2023-03-15T18:49:18+00:00
Bug 1812518 - Show the download dialog as an Android View
Tried to mimic the UX of a modal dialog while using Android Views.
This meant including a scrim that would consume all touches and theming the
navigation bar and status bar.
Avoiding a dialog and a separate window will allow the snackbar to see the
new "dialog" as a sibling in a CoordinatorLayout parent and so be able to
position itself based on the new "dialog".
This patch also added "start_download_dialog_layout" from A-C as it leads to
simpler and less code needed to style the layout - colors / shapes with
everything happening in XML versus calculating the values then setting them
programatically.
- - - - -
9eb98437 by Mugurell at 2023-03-15T18:49:50+00:00
Bug 1812518 - Show the 3rd party download dialog as an Android View
This uses the same direction as the before patch - inflating a new View that
can then serve as an anchor for the Snackbar.
Here we could use directly the AC layout as it needed no special customization.
- - - - -
94d239f2 by Mugurell at 2023-03-15T19:03:40+00:00
Bug 1812518 - Fix UI tests affected by the refactoring.
- - - - -
cf3ce1dd by Mugurell at 2023-03-15T19:06:08+00:00
Bug 1812518 - Update to latest AndroidComponents
The new version will contain the support for allowing to style download dialogs.
- - - - -
20 changed files:
- app/src/androidTest/java/org/mozilla/fenix/ui/CollectionTest.kt
- app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt
- app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt
- app/src/androidTest/java/org/mozilla/fenix/ui/robots/DownloadRobot.kt
- app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
- app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
- app/src/main/java/org/mozilla/fenix/components/FenixSnackbar.kt
- + app/src/main/java/org/mozilla/fenix/components/FenixSnackbarBehavior.kt
- + app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt
- + app/src/main/res/drawable/download_dialog_download_button_background.xml
- + app/src/main/res/layout/dialog_scrim.xml
- app/src/main/res/layout/fragment_browser.xml
- + app/src/main/res/layout/start_download_dialog_layout.xml
- app/src/main/res/values/colors.xml
- app/src/main/res/values/dimens.xml
- + app/src/test/java/org/mozilla/fenix/components/FenixSnackbarBehaviorTest.kt
- + app/src/test/java/org/mozilla/fenix/downloads/FirstPartyDownloadDialogTest.kt
- + app/src/test/java/org/mozilla/fenix/downloads/StartDownloadDialogTest.kt
- + app/src/test/java/org/mozilla/fenix/downloads/ThirdPartyDownloadDialogTest.kt
- app/src/test/java/org/mozilla/fenix/tabstray/ext/FenixSnackbarKtTest.kt
Changes:
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/CollectionTest.kt
=====================================
@@ -57,8 +57,10 @@ class CollectionTest {
featureSettingsHelper.resetAllFeatureFlags()
}
- @Test
+
// open a webpage, and add currently opened tab to existing collection
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812580")
+ @Test
fun mainMenuSaveToExistingCollection() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
@@ -84,6 +86,7 @@ class CollectionTest {
}
}
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812580")
@Test
fun verifyAddTabButtonOfCollectionMenu() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt
=====================================
@@ -75,7 +75,7 @@ class DownloadFileTypesTest(fileName: String) {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
- }.closePrompt {
+ }.closeCompletedDownloadPrompt {
}.openThreeDotMenu {
}.openDownloadsManager {
waitForDownloadsListToExist()
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt
=====================================
@@ -105,7 +105,7 @@ class DownloadTest {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
- }.closePrompt { }
+ }
mDevice.openNotification()
notificationShade {
verifySystemNotificationExists("Download completed")
=====================================
app/src/androidTest/java/org/mozilla/fenix/ui/robots/DownloadRobot.kt
=====================================
@@ -12,7 +12,6 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
-import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
@@ -82,6 +81,13 @@ class DownloadRobot {
return Transition()
}
+ fun closeCompletedDownloadPrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ closeCompletedDownloadButton().click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
fun closePrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
closePromptButton().click()
@@ -177,12 +183,14 @@ private fun assertDownloadNotificationPopup() {
)
}
+private fun closeCompletedDownloadButton() =
+ onView(withId(R.id.download_dialog_close_button))
+
private fun closePromptButton() =
- onView(withContentDescription("Close"))
+ onView(withId(R.id.close_button))
private fun downloadButton() =
onView(withText("Download"))
- .inRoot(isDialog())
.check(matches(isDisplayed()))
private fun openDownloadButton() =
=====================================
app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
=====================================
@@ -108,6 +108,9 @@ import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.downloads.DynamicDownloadDialog
+import org.mozilla.fenix.downloads.FirstPartyDownloadDialog
+import org.mozilla.fenix.downloads.StartDownloadDialog
+import org.mozilla.fenix.downloads.ThirdPartyDownloadDialog
import org.mozilla.fenix.ext.accessibilityManager
import org.mozilla.fenix.ext.breadcrumb
import org.mozilla.fenix.ext.components
@@ -213,6 +216,8 @@ abstract class BaseBrowserFragment :
@VisibleForTesting
internal val onboarding by lazy { FenixOnboarding(requireContext()) }
+ private var currentStartDownloadDialog: StartDownloadDialog? = null
+
@CallSuper
override fun onCreateView(
inflater: LayoutInflater,
@@ -345,7 +350,7 @@ abstract class BaseBrowserFragment :
}
viewLifecycleOwner.lifecycleScope.allowUndo(
- binding.browserLayout,
+ binding.dynamicSnackbarContainer,
snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo),
{
@@ -424,7 +429,7 @@ abstract class BaseBrowserFragment :
feature = ContextMenuFeature(
fragmentManager = parentFragmentManager,
store = store,
- candidates = getContextMenuCandidates(context, binding.browserLayout),
+ candidates = getContextMenuCandidates(context, binding.dynamicSnackbarContainer),
engineView = binding.engineView,
useCases = context.components.useCases.contextMenuUseCases,
tabId = customTabSessionId
@@ -493,7 +498,32 @@ abstract class BaseBrowserFragment :
),
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
- }
+ },
+ customFirstPartyDownloadDialog = { filename, contentSize, positiveAction, negativeAction ->
+ FirstPartyDownloadDialog(
+ activity = requireActivity(),
+ filename = filename.value,
+ contentSize = contentSize.value,
+ positiveButtonAction = positiveAction.value,
+ negativeButtonAction = negativeAction.value,
+ ).onDismiss {
+ currentStartDownloadDialog = null
+ }.show(binding.startDownloadDialogContainer).also {
+ currentStartDownloadDialog = it
+ }
+ },
+ customThirdPartyDownloadDialog = { downloaderApps, onAppSelected, negativeActionCallback ->
+ ThirdPartyDownloadDialog(
+ activity = requireActivity(),
+ downloaderApps = downloaderApps.value,
+ onAppSelected = onAppSelected.value,
+ negativeButtonAction = negativeActionCallback.value,
+ ).onDismiss {
+ currentStartDownloadDialog = null
+ }.show(binding.startDownloadDialogContainer).also {
+ currentStartDownloadDialog = it
+ }
+ },
)
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
@@ -512,7 +542,7 @@ abstract class BaseBrowserFragment :
didFail = downloadJobStatus == DownloadState.Status.FAILED,
tryAgain = downloadFeature::tryAgain,
onCannotOpenFile = {
- showCannotOpenFileError(binding.browserLayout, context, it)
+ showCannotOpenFileError(binding.dynamicSnackbarContainer, context, it)
},
binding = binding.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight
@@ -945,7 +975,7 @@ abstract class BaseBrowserFragment :
didFail = savedDownloadState.second,
tryAgain = onTryAgain,
onCannotOpenFile = {
- showCannotOpenFileError(binding.browserLayout, context, it)
+ showCannotOpenFileError(binding.dynamicSnackbarContainer, context, it)
},
binding = binding.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight,
@@ -1036,6 +1066,7 @@ abstract class BaseBrowserFragment :
it.selectedTab
}
.collect {
+ currentStartDownloadDialog?.dismiss()
handleTabSelected(it)
}
}
@@ -1104,6 +1135,7 @@ abstract class BaseBrowserFragment :
override fun onStop() {
super.onStop()
initUIJob?.cancel()
+ currentStartDownloadDialog?.dismiss()
requireComponents.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
?.let { session ->
@@ -1119,6 +1151,10 @@ abstract class BaseBrowserFragment :
return findInPageIntegration.onBackPressed() ||
fullScreenFeature.onBackPressed() ||
promptsFeature.onBackPressed() ||
+ currentStartDownloadDialog?.let {
+ it.dismiss()
+ true
+ } ?: false ||
sessionFeature.onBackPressed() ||
removeSessionIfNeeded()
}
@@ -1281,7 +1317,7 @@ abstract class BaseBrowserFragment :
withContext(Main) {
view?.let {
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = FenixSnackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
@@ -1303,7 +1339,7 @@ abstract class BaseBrowserFragment :
view?.let {
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = FenixSnackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
@@ -1346,7 +1382,7 @@ abstract class BaseBrowserFragment :
// Close find in page bar if opened
findInPageIntegration.onBackPressed()
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = false
)
@@ -1420,12 +1456,12 @@ abstract class BaseBrowserFragment :
}
private fun showCannotOpenFileError(
- view: View,
+ container: ViewGroup,
context: Context,
downloadState: DownloadState
) {
FenixSnackbar.make(
- view = view,
+ view = container,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
).setText(DynamicDownloadDialog.getCannotOpenFileErrorMessage(context, downloadState))
=====================================
app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
=====================================
@@ -347,7 +347,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
}
}
FenixSnackbar.make(
- view = binding.browserLayout,
+ view = binding.dynamicSnackbarContainer,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
=====================================
app/src/main/java/org/mozilla/fenix/components/FenixSnackbar.kt
=====================================
@@ -141,10 +141,20 @@ class FenixSnackbar private constructor(
0
}
)
+
+ if (parent.id == R.id.dynamicSnackbarContainer) {
+ (parent.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
+ behavior = FenixSnackbarBehavior<FrameLayout>(
+ context = view.context,
+ toolbarPosition = view.context.settings().toolbarPosition,
+ )
+ }
+ }
}
}
// Use the same implementation of `Snackbar`
+ @Suppress("ReturnCount")
private fun findSuitableParent(_view: View?): ViewGroup? {
var view = _view
var fallback: ViewGroup? = null
@@ -159,6 +169,10 @@ class FenixSnackbar private constructor(
return view
}
+ if (view.id == R.id.dynamicSnackbarContainer) {
+ return view
+ }
+
fallback = view
}
=====================================
app/src/main/java/org/mozilla/fenix/components/FenixSnackbarBehavior.kt
=====================================
@@ -0,0 +1,77 @@
+/* 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/. */
+
+package org.mozilla.fenix.components
+
+import android.content.Context
+import android.view.Gravity
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.toolbar.ToolbarPosition
+
+/**
+ * [CoordinatorLayout.Behavior] to be used by a snackbar that want to ensure it it always positioned
+ * such that it will be shown on top (vertically) of other siblings that may obstruct it's view.
+ *
+ * @param context [Context] used for various system interactions.
+ * @param toolbarPosition Where the toolbar is positioned on the screen.
+ * Depending on it's position (top / bottom) the snackbar will be shown below / above the toolbar.
+ */
+class FenixSnackbarBehavior<V : View>(
+ context: Context,
+ @get:VisibleForTesting internal val toolbarPosition: ToolbarPosition,
+) : CoordinatorLayout.Behavior<V>(context, null) {
+
+ private val dependenciesIds = listOf(
+ R.id.startDownloadDialogContainer,
+ R.id.viewDynamicDownloadDialog,
+ R.id.toolbar,
+ )
+
+ private var currentAnchorId: Int? = View.NO_ID
+
+ override fun layoutDependsOn(
+ parent: CoordinatorLayout,
+ child: V,
+ dependency: View,
+ ): Boolean {
+ val anchorId = dependenciesIds
+ .intersect(parent.children.filter { it.isVisible }.map { it.id }.toSet())
+ .firstOrNull()
+
+ // It is possible that previous anchor's visibility is changed.
+ // The layout is updated and layoutDependsOn is called but onDependentViewChanged not.
+ // We have to check here if a new anchor is available and reparent the snackbar.
+ // This check also ensures we are not positioning the snackbar multiple times for the same anchor.
+ return if (anchorId != currentAnchorId) {
+ positionSnackbar(child, parent.children.firstOrNull { it.id == anchorId })
+ true
+ } else {
+ false
+ }
+ }
+
+ private fun positionSnackbar(snackbar: View, dependency: View?) {
+ currentAnchorId = dependency?.id ?: View.NO_ID
+ val params = snackbar.layoutParams as CoordinatorLayout.LayoutParams
+
+ if (dependency == null || (dependency.id == R.id.toolbar && toolbarPosition == ToolbarPosition.TOP)) {
+ // Position the snackbar at the bottom of the screen.
+ params.anchorId = View.NO_ID
+ params.anchorGravity = Gravity.NO_GRAVITY
+ params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ } else {
+ // Position the snackbar just above the anchor.
+ params.anchorId = dependency.id
+ params.anchorGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ }
+
+ snackbar.layoutParams = params
+ }
+}
=====================================
app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt
=====================================
@@ -0,0 +1,248 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.app.Dialog
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.view.Window
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.annotation.VisibleForTesting
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.children
+import androidx.viewbinding.ViewBinding
+import mozilla.components.feature.downloads.databinding.MozacDownloaderChooserPromptBinding
+import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
+import mozilla.components.feature.downloads.ui.DownloaderApp
+import mozilla.components.feature.downloads.ui.DownloaderAppAdapter
+import mozilla.components.support.ktx.android.view.setNavigationBarTheme
+import mozilla.components.support.ktx.android.view.setStatusBarTheme
+import org.mozilla.fenix.R
+import org.mozilla.fenix.databinding.DialogScrimBinding
+import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
+import org.mozilla.fenix.ext.settings
+
+/**
+ * Parent of all download views that can mimic a modal [Dialog].
+ *
+ * @param activity The [Activity] in which the dialog will be shown.
+ * Used to update the activity [Window] to best mimic a modal dialog.
+ */
+abstract class StartDownloadDialog(
+ private val activity: Activity,
+) {
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ internal var binding: ViewBinding? = null
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ internal var container: ViewGroup? = null
+ private var scrim: DialogScrimBinding? = null
+
+ @VisibleForTesting
+ internal var onDismiss: () -> Unit = {}
+
+ @VisibleForTesting
+ internal var initialNavigationBarColor = activity.window.navigationBarColor
+
+ @VisibleForTesting
+ internal var initialStatusBarColor = activity.window.statusBarColor
+
+ /**
+ * Show the download view.
+ *
+ * @param container The [ViewGroup] in which the download view will be inflated.
+ */
+ fun show(container: ViewGroup): StartDownloadDialog {
+ this.container = container
+
+ val dialogParent = container.parent as? ViewGroup
+ dialogParent?.let {
+ scrim = DialogScrimBinding.inflate(LayoutInflater.from(activity), dialogParent, true).apply {
+ this.scrim.setOnClickListener {
+ // Empty listener needed to prevent clicking through.
+ }
+ }
+ }
+
+ setupView()
+
+ if (activity.settings().accessibilityServicesEnabled) {
+ disableSiblingsAccessibility(dialogParent)
+ }
+
+ container.apply {
+ val params = layoutParams as CoordinatorLayout.LayoutParams
+ params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ layoutParams = params
+
+ // Set a higher elevation than the toolbar sibling which we should cover.
+ elevation = activity.resources.getDimension(R.dimen.browser_fragment_download_dialog_elevation)
+ visibility = View.VISIBLE
+ }
+
+ activity.window.setNavigationBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+ activity.window.setStatusBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+
+ return this
+ }
+
+ /**
+ * Set a callback for when the download view is dismissed.
+ *
+ * @param callback The callback for when the view is dismissed.
+ */
+ fun onDismiss(callback: () -> Unit): StartDownloadDialog {
+ this.onDismiss = callback
+ return this
+ }
+
+ /**
+ * Immediately dismiss the current download view if it is shown.
+ * This will restore the previous UI removing any other layout / window customizations.
+ */
+ fun dismiss() {
+ scrim?.let {
+ (it.root.parent as? ViewGroup)?.removeView(it.root)
+ }
+ binding?.let {
+ (it.root.parent as? ViewGroup)?.removeView(it.root)
+ }
+ enableSiblingsAccessibility(container?.parent as? ViewGroup)
+
+ container?.visibility = View.GONE
+
+ activity.window.setNavigationBarTheme(initialNavigationBarColor)
+ activity.window.setStatusBarTheme(initialStatusBarColor)
+
+ onDismiss()
+ }
+
+ @VisibleForTesting
+ internal fun enableSiblingsAccessibility(parent: ViewGroup?) {
+ parent?.children
+ ?.filterNot { it.id == R.id.startDownloadDialogContainer }
+ ?.forEach {
+ ViewCompat.setImportantForAccessibility(
+ it,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
+ )
+ }
+ }
+
+ @VisibleForTesting
+ internal fun disableSiblingsAccessibility(parent: ViewGroup?) {
+ parent?.children
+ ?.filterNot { it.id == R.id.startDownloadDialogContainer }
+ ?.forEach {
+ ViewCompat.setImportantForAccessibility(
+ it,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+ )
+ }
+ }
+
+ /**
+ * Bind all download data to the download view.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ internal abstract fun setupView()
+}
+
+/**
+ * A download view mimicking a modal dialog that allows the user to download a file with the current application.
+ *
+ * @param activity The [Activity] in which the dialog will be shown.
+ * Used to update the activity [Window] to best mimic a modal dialog.
+ * @param filename Name of the file to be downloaded. It wil be shown without any modification.
+ * @param contentSize Size of the file to be downloaded expressed as a number of bytes.
+ * It will automatically be parsed to the appropriate kilobyte or megabyte value before being shown.
+ * @param positiveButtonAction Callback for when the user interacts with the dialog to start the download.
+ * @param negativeButtonAction Callback for when the user interacts with the dialog to dismiss it.
+ */
+class FirstPartyDownloadDialog(
+ private val activity: Activity,
+ private val filename: String,
+ private val contentSize: Long,
+ private val positiveButtonAction: () -> Unit,
+ private val negativeButtonAction: () -> Unit,
+) : StartDownloadDialog(activity) {
+ override fun setupView() {
+ val dialog = StartDownloadDialogLayoutBinding.inflate(LayoutInflater.from(activity), container, true)
+ .also { binding = it }
+
+ if (contentSize > 0L) {
+ val contentSize = contentSize.toMegabyteOrKilobyteString()
+ dialog.title.text =
+ activity.getString(R.string.mozac_feature_downloads_dialog_title2, contentSize)
+ }
+
+ dialog.filename.text = filename
+
+ dialog.downloadButton.setOnClickListener {
+ positiveButtonAction()
+ dismiss()
+ }
+
+ dialog.closeButton.setOnClickListener {
+ negativeButtonAction()
+ dismiss()
+ }
+
+ if (activity.settings().accessibilityServicesEnabled) {
+ // Ensure the title of the dialog is focused and read by talkback first.
+ dialog.root.viewTreeObserver.addOnGlobalLayoutListener(
+ object : OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ dialog.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ dialog.title.run {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)
+ performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
+ }
+ }
+ },
+ )
+ }
+ }
+}
+
+/**
+ * A download view mimicking a modal dialog that presents the user with a list of all apps
+ * that can handle the download request.
+ *
+ * @param activity The [Activity] in which the dialog will be shown.
+ * Used to update the activity [Window] to best mimic a modal dialog.
+ * @param downloaderApps List of all applications that can handle the download request.
+ * @param onAppSelected Callback for when the user chooses a specific application to handle the download request.
+ * @param negativeButtonAction Callback for when the user interacts with the dialog to dismiss it.
+ */
+class ThirdPartyDownloadDialog(
+ private val activity: Activity,
+ private val downloaderApps: List<DownloaderApp>,
+ private val onAppSelected: (DownloaderApp) -> Unit,
+ private val negativeButtonAction: () -> Unit,
+) : StartDownloadDialog(activity) {
+ override fun setupView() {
+ val dialog = MozacDownloaderChooserPromptBinding.inflate(LayoutInflater.from(activity), container, true)
+ .also { binding = it }
+
+ val recyclerView = dialog.appsList
+ recyclerView.adapter = DownloaderAppAdapter(activity, downloaderApps) { app ->
+ onAppSelected(app)
+ dismiss()
+ }
+
+ dialog.closeButton.setOnClickListener {
+ negativeButtonAction()
+ dismiss()
+ }
+ }
+}
=====================================
app/src/main/res/drawable/download_dialog_download_button_background.xml
=====================================
@@ -0,0 +1,9 @@
+<?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/. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/bottom_sheet_corner_radius"/>
+ <solid android:color="?attr/accent" />
+</shape>
=====================================
app/src/main/res/layout/dialog_scrim.xml
=====================================
@@ -0,0 +1,15 @@
+<?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/. -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/material_scrim_color"
+ android:clipToPadding="false"
+ android:fitsSystemWindows="true"
+ android:importantForAccessibility="no"
+ android:soundEffectsEnabled="false" />
=====================================
app/src/main/res/layout/fragment_browser.xml
=====================================
@@ -66,6 +66,20 @@
android:layout_height="match_parent"
android:visibility="gone" />
+ <FrameLayout
+ android:id="@+id/startDownloadDialogContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:visibility="gone"
+ android:elevation="@dimen/browser_fragment_toolbar_elevation"/>
+
+ <FrameLayout
+ android:id="@+id/dynamicSnackbarContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="@dimen/browser_fragment_toolbar_elevation"/>
+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<mozilla.components.feature.prompts.creditcard.CreditCardSelectBar
=====================================
app/src/main/res/layout/start_download_dialog_layout.xml
=====================================
@@ -0,0 +1,96 @@
+<?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/. -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/dialogLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:windowBackground"
+ android:orientation="vertical"
+ app:layout_constraintBottom_toBottomOf="parent">
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_alignParentTop="true"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:importantForAccessibility="no"
+ android:scaleType="center"
+ app:srcCompat="@drawable/mozac_feature_download_ic_download"
+ app:tint="?android:attr/textColorPrimary" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBaseline="@id/icon"
+ android:layout_alignParentTop="true"
+ android:layout_marginStart="3dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="11dp"
+ android:layout_toStartOf="@id/close_button"
+ android:layout_toEndOf="@id/icon"
+ android:paddingStart="5dp"
+ android:paddingTop="4dp"
+ android:paddingEnd="5dp"
+ android:text="@string/mozac_feature_downloads_dialog_download"
+ android:textColor="?android:attr/textColorPrimary"
+ tools:text="Download (85.7 MB)"
+ tools:textColor="#000000" />
+
+ <androidx.appcompat.widget.AppCompatImageButton
+ android:id="@+id/close_button"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignBaseline="@id/icon"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginStart="3dp"
+ android:scaleType="centerInside"
+ android:background="@null"
+ android:contentDescription="@string/mozac_feature_downloads_button_close"
+ app:srcCompat="@drawable/mozac_ic_close"
+ app:tint="?android:attr/textColorPrimary"
+ tools:textColor="#000000" />
+
+ <TextView
+ android:id="@+id/filename"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignBaseline="@id/icon"
+ android:layout_marginStart="3dp"
+ android:layout_marginTop="16dp"
+ android:layout_toEndOf="@id/icon"
+ android:paddingStart="5dp"
+ android:paddingTop="4dp"
+ android:paddingEnd="5dp"
+ android:textColor="?android:attr/textColorPrimary"
+ tools:text="@tools:sample/lorem/random"
+ tools:textColor="#000000" />
+
+ <Button
+ android:id="@+id/download_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/filename"
+ android:layout_alignParentEnd="true"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="16dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:text="@string/mozac_feature_downloads_dialog_download"
+ android:background="@drawable/download_dialog_download_button_background"
+ android:textColor="?attr/textOnColorPrimary"
+ android:textAllCaps="false"
+ tools:ignore="ButtonStyleXmlDetector" />
+</RelativeLayout>
=====================================
app/src/main/res/values/colors.xml
=====================================
@@ -337,4 +337,7 @@
<!-- App Spinners colors -->
<color name="spinner_selected_item">#1415141A</color>
+
+ <!-- Material Design colors -->
+ <color name="material_scrim_color">#52000000</color>
</resources>
=====================================
app/src/main/res/values/dimens.xml
=====================================
@@ -82,6 +82,8 @@
<!--The size of the gap between the tab preview and content layout.-->
<dimen name="browser_fragment_gesture_preview_offset">48dp</dimen>
<dimen name="browser_fragment_toolbar_elevation">16dp</dimen>
+ <!-- The download dialogs are shown above the toolbar so they need a bigger elevation. -->
+ <dimen name="browser_fragment_download_dialog_elevation">17dp</dimen>
<!-- Search Fragment -->
<dimen name="search_fragment_clipboard_item_height">56dp</dimen>
=====================================
app/src/test/java/org/mozilla/fenix/components/FenixSnackbarBehaviorTest.kt
=====================================
@@ -0,0 +1,251 @@
+/* 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/. */
+
+package org.mozilla.fenix.components
+
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.toolbar.ToolbarPosition
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+
+@RunWith(FenixRobolectricTestRunner::class)
+class FenixSnackbarBehaviorTest {
+ private val snackbarParams = CoordinatorLayout.LayoutParams(0, 0)
+ private val snackbarContainer = FrameLayout(testContext)
+ private val dependency = View(testContext)
+ private val parent = CoordinatorLayout(testContext)
+
+ @Before
+ fun setup() {
+ snackbarContainer.layoutParams = snackbarParams
+ parent.addView(dependency)
+ }
+
+ @Test
+ fun `GIVEN no valid anchors are shown WHEN the snackbar is shown THEN don't anchor it`() {
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ @Test
+ fun `GIVEN the dynamic download dialog is shown WHEN the snackbar is shown THEN place the snackbar above the dialog`() {
+ dependency.id = R.id.viewDynamicDownloadDialog
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor()
+ }
+
+ @Test
+ fun `GIVEN a bottom toolbar is shown WHEN the snackbar is shown THEN place the snackbar above the toolbar`() {
+ dependency.id = R.id.toolbar
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor()
+ }
+
+ @Test
+ fun `GIVEN a toolbar and a dynamic download dialog are shown WHEN the snackbar is shown THEN place the snackbar above the dialog`() {
+ listOf(R.id.viewDynamicDownloadDialog, R.id.toolbar).forEach {
+ parent.addView(View(testContext).apply { id = it })
+ }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor(parent.findViewById(R.id.viewDynamicDownloadDialog))
+ }
+
+ @Test
+ fun `GIVEN a toolbar, a download dialog and a dynamic download dialog are shown WHEN the snackbar is shown THEN place the snackbar above the download dialog`() {
+ listOf(R.id.viewDynamicDownloadDialog, R.id.toolbar, R.id.startDownloadDialogContainer).forEach {
+ parent.addView(View(testContext).apply { id = it })
+ }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+
+ assertSnackbarPlacementAboveAnchor(parent.findViewById(R.id.startDownloadDialogContainer))
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to the dynamic download dialog and a bottom toolbar is shown WHEN the dialog is not shown anymore THEN place the snackbar above the toolbar`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.viewDynamicDownloadDialog }
+ .also { parent.addView(it) }
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to a download dialog and another dynamic dialog is shown WHEN the dialog is not shown anymore THEN place the snackbar above the dynamic dialog`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.startDownloadDialogContainer }
+ .also { parent.addView(it) }
+ val dynamicDialog = View(testContext)
+ .apply { id = R.id.viewDynamicDownloadDialog }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dynamicDialog)
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dynamicDialog)
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to a download dialog and a bottom toolbar is shown WHEN the dialog is not shown anymore THEN place the snackbar above the toolbar`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.startDownloadDialogContainer }
+ .also { parent.addView(it) }
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to the bottom toolbar WHEN the toolbar is not shown anymore THEN place the snackbar at the bottom`() {
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.BOTTOM)
+
+ // Test the scenario where the toolbar is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ toolbar.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+
+ // Test the scenario where the toolbar is removed from parent.
+ toolbar.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(toolbar)
+ parent.removeView(toolbar)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored to the dynamic download dialog and a top toolbar is shown WHEN the dialog is not shown anymore THEN place the snackbar to the bottom`() {
+ val dialog = View(testContext)
+ .apply { id = R.id.viewDynamicDownloadDialog }
+ .also { parent.addView(it) }
+ View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.TOP)
+
+ // Test the scenario where the dialog is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ dialog.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+
+ // Test the scenario where the dialog is removed from parent.
+ dialog.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarPlacementAboveAnchor(dialog)
+ parent.removeView(dialog)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ @Test
+ fun `GIVEN the snackbar is anchored based on a top toolbar WHEN the toolbar is not shown anymore THEN place the snackbar at the bottom`() {
+ val toolbar = View(testContext)
+ .apply { id = R.id.toolbar }
+ .also { parent.addView(it) }
+ val behavior = FenixSnackbarBehavior<ViewGroup>(testContext, ToolbarPosition.TOP)
+
+ // Test the scenario where the toolbar is invisible.
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ toolbar.visibility = View.GONE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+
+ // Test the scenario where the toolbar is removed from parent.
+ toolbar.visibility = View.VISIBLE
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ parent.removeView(toolbar)
+ behavior.layoutDependsOn(parent, snackbarContainer, dependency)
+ assertSnackbarIsPlacedAtTheBottomOfTheScreen()
+ }
+
+ private fun assertSnackbarPlacementAboveAnchor(anchor: View = dependency) {
+ assertEquals(anchor.id, snackbarContainer.params.anchorId)
+ assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, snackbarContainer.params.anchorGravity)
+ assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, snackbarContainer.params.gravity)
+ }
+
+ private fun assertSnackbarIsPlacedAtTheBottomOfTheScreen() {
+ assertEquals(View.NO_ID, snackbarContainer.params.anchorId)
+ assertEquals(Gravity.NO_GRAVITY, snackbarContainer.params.anchorGravity)
+ assertEquals(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL, snackbarContainer.params.gravity)
+ }
+
+ private val FrameLayout.params
+ get() = layoutParams as CoordinatorLayout.LayoutParams
+}
=====================================
app/src/test/java/org/mozilla/fenix/downloads/FirstPartyDownloadDialogTest.kt
=====================================
@@ -0,0 +1,115 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.widget.FrameLayout
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.spyk
+import io.mockk.verify
+import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.robolectric.Robolectric
+
+@RunWith(FenixRobolectricTestRunner::class)
+class FirstPartyDownloadDialogTest {
+ private val activity: Activity = Robolectric.buildActivity(Activity::class.java).create().get()
+
+ @Before
+ fun setup() {
+ every { activity.settings().accessibilityServicesEnabled } returns false
+ }
+
+ @Test
+ fun `GIVEN the size of the download is known WHEN setting it's View THEN bind all provided download data and show the download size`() {
+ var wasPositiveActionDone = false
+ var wasNegativeActionDone = false
+ val contentSize = 5566L
+ val dialog = spyk(
+ FirstPartyDownloadDialog(
+ activity = activity,
+ filename = "Test",
+ contentSize = contentSize,
+ positiveButtonAction = { wasPositiveActionDone = true },
+ negativeButtonAction = { wasNegativeActionDone = true },
+ ),
+ )
+ every { dialog.dismiss() } just Runs
+ val dialogParent = FrameLayout(testContext)
+ dialog.container = dialogParent
+
+ dialog.setupView()
+
+ assertEquals(1, dialogParent.childCount)
+ assertEquals(R.id.dialogLayout, dialogParent.getChildAt(0).id)
+ val dialogBinding = dialog.binding as StartDownloadDialogLayoutBinding
+ assertEquals(
+ testContext.getString(
+ R.string.mozac_feature_downloads_dialog_title2,
+ contentSize.toMegabyteOrKilobyteString(),
+ ),
+ dialogBinding.title.text,
+ )
+ assertEquals("Test", dialogBinding.filename.text)
+ assertFalse(wasPositiveActionDone)
+ assertFalse(wasNegativeActionDone)
+ dialogBinding.downloadButton.callOnClick()
+ verify { dialog.dismiss() }
+ assertTrue(wasPositiveActionDone)
+ dialogBinding.closeButton.callOnClick()
+ verify(exactly = 2) { dialog.dismiss() }
+ assertTrue(wasNegativeActionDone)
+ }
+
+ @Test
+ fun `GIVEN the size of the download is not known WHEN setting it's View THEN bind all provided download data and show the download size`() {
+ var wasPositiveActionDone = false
+ var wasNegativeActionDone = false
+ val contentSize = 0L
+ val dialog = spyk(
+ FirstPartyDownloadDialog(
+ activity = activity,
+ filename = "Test",
+ contentSize = contentSize,
+ positiveButtonAction = { wasPositiveActionDone = true },
+ negativeButtonAction = { wasNegativeActionDone = true },
+ ),
+ )
+ every { dialog.dismiss() } just Runs
+ val dialogParent = FrameLayout(testContext)
+ dialog.container = dialogParent
+
+ dialog.setupView()
+
+ assertEquals(1, dialogParent.childCount)
+ assertEquals(R.id.dialogLayout, dialogParent.getChildAt(0).id)
+ val dialogBinding = dialog.binding as StartDownloadDialogLayoutBinding
+ assertEquals(
+ testContext.getString(R.string.mozac_feature_downloads_dialog_download),
+ dialogBinding.title.text,
+ )
+ assertEquals("Test", dialogBinding.filename.text)
+ assertFalse(wasPositiveActionDone)
+ assertFalse(wasNegativeActionDone)
+ dialogBinding.downloadButton.callOnClick()
+ verify { dialog.dismiss() }
+ assertTrue(wasPositiveActionDone)
+ dialogBinding.closeButton.callOnClick()
+ verify(exactly = 2) { dialog.dismiss() }
+ assertTrue(wasNegativeActionDone)
+ }
+}
=====================================
app/src/test/java/org/mozilla/fenix/downloads/StartDownloadDialogTest.kt
=====================================
@@ -0,0 +1,242 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Color
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.verify
+import mozilla.components.support.ktx.android.view.setNavigationBarTheme
+import mozilla.components.support.ktx.android.view.setStatusBarTheme
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.mozilla.fenix.utils.Settings
+import org.robolectric.Robolectric
+
+@RunWith(FenixRobolectricTestRunner::class)
+class StartDownloadDialogTest {
+ @Test
+ fun `WHEN the dialog is instantiated THEN cache the navigation and status bar colors`() {
+ val navigationBarColor = Color.RED
+ val statusBarColor = Color.BLUE
+ val activity: Activity = mockk {
+ every { window.navigationBarColor } returns navigationBarColor
+ every { window.statusBarColor } returns statusBarColor
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ assertEquals(navigationBarColor, dialog.initialNavigationBarColor)
+ assertEquals(statusBarColor, dialog.initialStatusBarColor)
+ }
+
+ @Test
+ fun `WHEN the view is to be shown THEN set the scrim and other window customization bind the download values`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ mockkStatic("mozilla.components.support.ktx.android.view.WindowKt", "org.mozilla.fenix.ext.ContextKt") {
+ every { any<Context>().settings() } returns mockk(relaxed = true)
+ val fluentDialog = dialog.show(dialogContainer)
+
+ val scrim = dialogParent.children.first { it.id == R.id.scrim }
+ assertTrue(scrim.hasOnClickListeners())
+ assertFalse(scrim.isSoundEffectsEnabled)
+ assertTrue(dialog.wasDownloadDataBinded)
+ assertEquals(
+ Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL,
+ (dialogContainer.layoutParams as CoordinatorLayout.LayoutParams).gravity,
+ )
+ assertEquals(
+ testContext.resources.getDimension(R.dimen.browser_fragment_download_dialog_elevation),
+ dialogContainer.elevation,
+ )
+ assertTrue(dialogContainer.isVisible)
+ verify {
+ activity.window.setNavigationBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+ activity.window.setStatusBarTheme(ContextCompat.getColor(activity, R.color.material_scrim_color))
+ }
+ assertEquals(dialog, fluentDialog)
+ }
+ }
+
+ @Test
+ fun `GIVEN a dismiss callback WHEN the dialog is dismissed THEN the callback is informed`() {
+ var wasDismissCalled = false
+ val dialog = TestDownloadDialog(mockk(relaxed = true))
+
+ val fluentDialog = dialog.onDismiss { wasDismissCalled = true }
+ dialog.onDismiss()
+
+ assertTrue(wasDismissCalled)
+ assertEquals(dialog, fluentDialog)
+ }
+
+ @Test
+ fun `GIVEN the download dialog is shown WHEN dismissed THEN remove the scrim, the dialog and any window customizations`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ }
+ val dialog = TestDownloadDialog(activity)
+ mockkStatic("mozilla.components.support.ktx.android.view.WindowKt", "org.mozilla.fenix.ext.ContextKt") {
+ every { any<Context>().settings() } returns mockk(relaxed = true)
+ dialog.show(dialogContainer)
+ dialog.binding = StartDownloadDialogLayoutBinding
+ .inflate(LayoutInflater.from(activity), dialogContainer, true)
+
+ dialog.dismiss()
+
+ assertNull(dialogParent.children.firstOrNull { it.id == R.id.scrim })
+ assertTrue(dialogParent.childCount == 1)
+ assertTrue(dialogContainer.childCount == 0)
+ assertFalse(dialogContainer.isVisible)
+ verify {
+ activity.window.setNavigationBarTheme(dialog.initialNavigationBarColor)
+ activity.window.setStatusBarTheme(dialog.initialStatusBarColor)
+ }
+ }
+ }
+
+ @Test
+ fun `GIVEN a ViewGroup WHEN enabling accessibility THEN enable it for all children but the dialog container`() {
+ val activity: Activity = mockk(relaxed = true)
+ val dialogParent = FrameLayout(testContext)
+ FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
+ val otherView = View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ dialog.enableSiblingsAccessibility(dialogParent)
+
+ assertEquals(listOf(otherView), dialogParent.children.filter { it.isImportantForAccessibility }.toList())
+ }
+
+ @Test
+ fun `GIVEN a ViewGroup WHEN disabling accessibility THEN disable it for all children but the dialog container`() {
+ val activity: Activity = mockk(relaxed = true)
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ val dialog = TestDownloadDialog(activity)
+
+ dialog.disableSiblingsAccessibility(dialogParent)
+
+ assertEquals(listOf(dialogContainer), dialogParent.children.filter { it.isImportantForAccessibility }.toList())
+ }
+
+ @Test
+ fun `GIVEN accessibility services are enabled WHEN the dialog is shown THEN disable siblings accessibility`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+
+ mockkStatic("org.mozilla.fenix.ext.ContextKt") {
+ val dialog = TestDownloadDialog(activity)
+
+ val settings: Settings = mockk {
+ every { accessibilityServicesEnabled } returns false
+ }
+ every { any<Context>().settings() } returns settings
+ dialog.show(dialogContainer)
+ assertEquals(2, dialogParent.children.count { it.isImportantForAccessibility })
+
+ every { settings.accessibilityServicesEnabled } returns true
+ dialog.show(dialogContainer)
+ assertEquals(listOf(dialogContainer), dialogParent.children.filter { it.isImportantForAccessibility }.toList())
+ }
+ }
+
+ @Test
+ fun `WHEN the dialog is dismissed THEN re-enable siblings accessibility`() {
+ val activity = Robolectric.buildActivity(Activity::class.java).create().get()
+ val dialogParent = FrameLayout(testContext)
+ val dialogContainer = FrameLayout(testContext).also {
+ dialogParent.addView(it)
+ it.id = R.id.startDownloadDialogContainer
+ it.layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ val accessibleView = View(testContext).also {
+ dialogParent.addView(it)
+ it.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+ mockkStatic("org.mozilla.fenix.ext.ContextKt") {
+ val settings: Settings = mockk {
+ every { accessibilityServicesEnabled } returns true
+ }
+ every { any<Context>().settings() } returns settings
+ val dialog = TestDownloadDialog(activity)
+ dialog.show(dialogContainer)
+ dialog.binding = StartDownloadDialogLayoutBinding
+ .inflate(LayoutInflater.from(activity), dialogContainer, true)
+
+ dialog.dismiss()
+
+ assertEquals(
+ listOf(accessibleView),
+ dialogParent.children.filter { it.isVisible && it.isImportantForAccessibility }.toList(),
+ )
+ }
+ }
+}
+
+private class TestDownloadDialog(
+ activity: Activity,
+) : StartDownloadDialog(activity) {
+ var wasDownloadDataBinded = false
+
+ override fun setupView() {
+ wasDownloadDataBinded = true
+ }
+}
=====================================
app/src/test/java/org/mozilla/fenix/downloads/ThirdPartyDownloadDialogTest.kt
=====================================
@@ -0,0 +1,54 @@
+/* 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/. */
+
+package org.mozilla.fenix.downloads
+
+import android.app.Activity
+import android.widget.FrameLayout
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.spyk
+import io.mockk.verify
+import mozilla.components.feature.downloads.databinding.MozacDownloaderChooserPromptBinding
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.robolectric.Robolectric
+
+@RunWith(FenixRobolectricTestRunner::class)
+class ThirdPartyDownloadDialogTest {
+ private val activity: Activity = Robolectric.buildActivity(Activity::class.java).create().get()
+
+ @Test
+ fun `GIVEN a list of downloader apps WHEN setting it's View THEN bind all provided download data`() {
+ var wasNegativeActionDone = false
+ val dialog = spyk(
+ ThirdPartyDownloadDialog(
+ activity = activity,
+ downloaderApps = listOf(mockk(), mockk()),
+ onAppSelected = { /* cannot test the viewholder click */ },
+ negativeButtonAction = { wasNegativeActionDone = true },
+ ),
+ )
+ every { dialog.dismiss() } just Runs
+ val dialogParent = FrameLayout(testContext)
+ dialog.container = dialogParent
+
+ dialog.setupView()
+
+ assertEquals(1, dialogParent.childCount)
+ assertEquals(R.id.relativeLayout, dialogParent.getChildAt(0).id)
+ val dialogBinding = dialog.binding as MozacDownloaderChooserPromptBinding
+ assertEquals(2, dialogBinding.appsList.adapter?.itemCount)
+ dialogBinding.closeButton.callOnClick()
+ assertTrue(wasNegativeActionDone)
+ verify { dialog.dismiss() }
+ }
+}
=====================================
app/src/test/java/org/mozilla/fenix/tabstray/ext/FenixSnackbarKtTest.kt
=====================================
@@ -6,16 +6,29 @@ package org.mozilla.fenix.tabstray.ext
import android.content.Context
import android.view.View
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
import io.mockk.every
import io.mockk.mockk
+import io.mockk.mockkStatic
import io.mockk.verifyOrder
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
+import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
+import org.mozilla.fenix.components.FenixSnackbarBehavior
+import org.mozilla.fenix.components.toolbar.ToolbarPosition
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.helpers.MockkRetryTestRule
import org.mozilla.fenix.tabstray.TabsTrayFragment.Companion.ELEVATION
+import org.mozilla.fenix.utils.Settings
+@RunWith(FenixRobolectricTestRunner::class)
class FenixSnackbarKtTest {
@get:Rule
@@ -94,4 +107,24 @@ class FenixSnackbarKtTest {
snackbar.setAction("test1", any())
}
}
+
+ @Test
+ fun `GIVEN the snackbar is a child of dynamic container WHEN it is shown THEN enable the dynamic behavior`() {
+ val container = FrameLayout(testContext).apply {
+ id = R.id.dynamicSnackbarContainer
+ layoutParams = CoordinatorLayout.LayoutParams(0, 0)
+ }
+ val settings: Settings = mockk(relaxed = true) {
+ every { toolbarPosition } returns ToolbarPosition.BOTTOM
+ }
+ mockkStatic("org.mozilla.fenix.ext.ContextKt") {
+ every { any<Context>().settings() } returns settings
+
+ FenixSnackbar.make(view = container)
+
+ val behavior = (container.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior
+ assertTrue(behavior is FenixSnackbarBehavior)
+ assertEquals(ToolbarPosition.BOTTOM, (behavior as? FenixSnackbarBehavior)?.toolbarPosition)
+ }
+ }
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/compare/25e5afc9e6e8…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/fenix/-/compare/25e5afc9e6e8…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/android-components][android-components-102.0.14-12.5-1] 4 commits: Bug 1783561 - Stop BrowserToolbarBehavior positioning the snackbar
by Richard Pospesel (@richard) 16 Mar '23
by Richard Pospesel (@richard) 16 Mar '23
16 Mar '23
Richard Pospesel pushed to branch android-components-102.0.14-12.5-1 at The Tor Project / Applications / android-components
Commits:
52f79946 by Mugurell at 2023-03-16T12:04:07+00:00
Bug 1783561 - Stop BrowserToolbarBehavior positioning the snackbar
The snackbar is placed depending on the toolbar so the relation should be
reversed. The toolbar should not know about and control the snackbar.
There could be other siblings that the snackbar wants to position itself by
and so removing this responsability from BrowserToolbarBehavior will help
better support current and future flows.
Backport of https://github.com/mozilla-mobile/firefox-android/commit/04a464e66a95892d9a…
- - - - -
4ec68dfa by Mugurell at 2023-03-16T12:04:07+00:00
Bug 1812518 - Allow a custom View for first party downloads
This will allow clients easily implement their own UI.
Backport of https://github.com/mozilla-mobile/firefox-android/commit/e678610bc10df96b85…
- - - - -
9589ff50 by Mugurell at 2023-03-16T12:04:07+00:00
Bug 1783561 - Allow a custom View for 3rd party downloads
This will allow clients easily implement their own UI
Backport of https://github.com/mozilla-mobile/firefox-android/commit/c53dd65718579006e1…
- - - - -
ccafd6d1 by Mugurell at 2023-03-16T12:04:07+00:00
Improve prompts UX
Backport of https://github.com/mozilla-mobile/firefox-android/commit/1dc21a3786506200be…
- - - - -
8 changed files:
- components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehavior.kt
- components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehaviorTest.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadDialogFragment.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ext/DownloadState.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ui/DownloaderAppAdapter.kt
- components/feature/prompts/build.gradle
- components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
Changes:
=====================================
components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehavior.kt
=====================================
@@ -6,19 +6,15 @@ package mozilla.components.browser.toolbar.behavior
import android.content.Context
import android.util.AttributeSet
-import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
-import com.google.android.material.snackbar.Snackbar
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.concept.engine.EngineView
import mozilla.components.support.ktx.android.view.findViewInHierarchy
-private const val SMALL_ELEVATION_CHANGE = 0.01f
-
/**
* Where the toolbar is placed on the screen.
*/
@@ -35,7 +31,6 @@ enum class ToolbarPosition {
*
* This implementation will:
* - Show/Hide the [BrowserToolbar] automatically when scrolling vertically.
- * - On showing a [Snackbar] position it above the [BrowserToolbar].
* - Snap the [BrowserToolbar] to be hidden or visible when the user stops scrolling.
*/
class BrowserToolbarBehavior(
@@ -128,14 +123,6 @@ class BrowserToolbarBehavior(
return false // allow events to be passed to below listeners
}
- override fun layoutDependsOn(parent: CoordinatorLayout, child: BrowserToolbar, dependency: View): Boolean {
- if (toolbarPosition == ToolbarPosition.BOTTOM && dependency is Snackbar.SnackbarLayout) {
- positionSnackbar(child, dependency)
- }
-
- return super.layoutDependsOn(parent, child, dependency)
- }
-
override fun onLayoutChild(
parent: CoordinatorLayout,
child: BrowserToolbar,
@@ -179,23 +166,6 @@ class BrowserToolbarBehavior(
isScrollEnabled = false
}
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- internal fun positionSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
- val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams
-
- // Position the snackbar above the toolbar so that it doesn't overlay the toolbar.
- params.anchorId = child.id
- params.anchorGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
- params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
-
- snackbarLayout.layoutParams = params
-
- // In order to avoid the snackbar casting a shadow on the toolbar we adjust the elevation of the snackbar here.
- // We still place it slightly behind the toolbar so that it will not animate over the toolbar but instead pop
- // out from under the toolbar.
- snackbarLayout.elevation = child.elevation - SMALL_ELEVATION_CHANGE
- }
-
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun tryToScrollVertically(distance: Float) {
browserToolbar?.let { toolbar ->
=====================================
components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehaviorTest.kt
=====================================
@@ -6,14 +6,12 @@ package mozilla.components.browser.toolbar.behavior
import android.content.Context
import android.graphics.Bitmap
-import android.view.Gravity
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_MOVE
import android.widget.FrameLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.android.material.snackbar.Snackbar
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
@@ -474,29 +472,6 @@ class BrowserToolbarBehaviorTest {
verify(yTranslator).collapseWithAnimation(toolbar)
}
- @Test
- fun `Behavior will position snackbar above toolbar`() {
- val behavior = BrowserToolbarBehavior(testContext, null, ToolbarPosition.BOTTOM)
-
- val toolbar: BrowserToolbar = mock()
- doReturn(4223).`when`(toolbar).id
-
- val layoutParams: CoordinatorLayout.LayoutParams = CoordinatorLayout.LayoutParams(0, 0)
-
- val snackbarLayout: Snackbar.SnackbarLayout = mock()
- doReturn(layoutParams).`when`(snackbarLayout).layoutParams
-
- behavior.layoutDependsOn(
- parent = mock(),
- child = toolbar,
- dependency = snackbarLayout
- )
-
- assertEquals(4223, layoutParams.anchorId)
- assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, layoutParams.anchorGravity)
- assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, layoutParams.gravity)
- }
-
@Test
fun `Behavior will forceExpand when scrolling up and !shouldScroll if the touch was handled in the browser`() {
val behavior = spy(BrowserToolbarBehavior(testContext, null, ToolbarPosition.BOTTOM))
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadDialogFragment.kt
=====================================
@@ -10,7 +10,7 @@ import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.BYTES_TO_MB_LIMIT
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.KILOBYTE
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.MEGABYTE
-import mozilla.components.support.utils.DownloadUtils
+import mozilla.components.feature.downloads.ext.realFilenameOrGuessed
/**
* This is a general representation of a dialog meant to be used in collaboration with [DownloadsFeature]
@@ -34,11 +34,7 @@ abstract class DownloadDialogFragment : AppCompatDialogFragment() {
*/
fun setDownload(download: DownloadState) {
val args = arguments ?: Bundle()
- args.putString(
- KEY_FILE_NAME,
- download.fileName
- ?: DownloadUtils.guessFileName(null, download.destinationDirectory, download.url, download.contentType)
- )
+ args.putString(KEY_FILE_NAME, download.realFilenameOrGuessed)
args.putString(KEY_URL, download.url)
args.putLong(KEY_CONTENT_LENGTH, download.contentLength ?: 0)
arguments = args
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
=====================================
@@ -25,6 +25,7 @@ import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.FRAGMENT_TAG
+import mozilla.components.feature.downloads.ext.realFilenameOrGuessed
import mozilla.components.feature.downloads.manager.DownloadManager
import mozilla.components.feature.downloads.manager.noop
import mozilla.components.feature.downloads.manager.onDownloadStopped
@@ -41,6 +42,43 @@ import mozilla.components.support.ktx.kotlin.isSameOriginAs
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.support.utils.Browsers
+/**
+ * The name of the file to be downloaded.
+ */
+@JvmInline
+value class Filename(val value: String)
+
+/**
+ * The size of the file to be downloaded expressed as the number of `bytes`.
+ * The value will be `0` if the size is unknown.
+ */
+@JvmInline
+value class ContentSize(val value: Long)
+
+/**
+ * The list of all applications that can perform a download, including this application.
+ */
+@JvmInline
+value class ThirdPartyDownloaderApps(val value: List<DownloaderApp>)
+
+/**
+ * Callback for when the user picked a certain application with which to download the current file.
+ */
+@JvmInline
+value class ThirdPartyDownloaderAppChosenCallback(val value: (DownloaderApp) -> Unit)
+
+/**
+ * Callback for when the positive button of a download dialog was tapped.
+ */
+@JvmInline
+value class PositiveActionCallback(val value: () -> Unit)
+
+/**
+ * Callback for when the negative button of a download dialog was tapped.
+ */
+@JvmInline
+value class NegativeActionCallback(val value: () -> Unit)
+
/**
* Feature implementation to provide download functionality for the selected
* session. The feature will subscribe to the selected session and listen
@@ -60,6 +98,10 @@ import mozilla.components.support.utils.Browsers
* @property promptsStyling styling properties for the dialog.
* @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
* if there are multiple apps a chooser dialog will shown.
+ * @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
+ * that will be processed by the current application.
+ * @property customThirdPartyDownloadDialog An optional delegate for showing a dialog for a download
+ * that can be processed by multiple installed applications including the current one.
*/
@Suppress("LongParameterList", "LargeClass")
class DownloadsFeature(
@@ -73,7 +115,11 @@ class DownloadsFeature(
private val tabId: String? = null,
private val fragmentManager: FragmentManager? = null,
private val promptsStyling: PromptsStyling? = null,
- private val shouldForwardToThirdParties: () -> Boolean = { false }
+ private val shouldForwardToThirdParties: () -> Boolean = { false },
+ private val customFirstPartyDownloadDialog:
+ ((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
+ private val customThirdPartyDownloadDialog:
+ ((ThirdPartyDownloaderApps, ThirdPartyDownloaderAppChosenCallback, NegativeActionCallback) -> Unit)? = null,
) : LifecycleAwareFeature, PermissionsFeature {
var onDownloadStopped: onDownloadStopped
@@ -159,16 +205,45 @@ class DownloadsFeature(
val shouldShowAppDownloaderDialog = shouldForwardToThirdParties() && apps.size > 1
return if (shouldShowAppDownloaderDialog) {
- showAppDownloaderDialog(tab, download, apps)
+ when (customThirdPartyDownloadDialog) {
+ null -> showAppDownloaderDialog(tab, download, apps)
+ else -> customThirdPartyDownloadDialog.invoke(
+ ThirdPartyDownloaderApps(apps),
+ ThirdPartyDownloaderAppChosenCallback {
+ onDownloaderAppSelected(it, tab, download)
+ },
+ NegativeActionCallback {
+ useCases.cancelDownloadRequest.invoke(tab.id, download.id)
+ },
+ )
+ }
+
false
} else {
if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
- if (fragmentManager != null && !download.skipConfirmation) {
- showDownloadDialog(tab, download)
- false
- } else {
- useCases.consumeDownload(tab.id, download.id)
- startDownload(download)
+ when {
+ customFirstPartyDownloadDialog != null && !download.skipConfirmation -> {
+ customFirstPartyDownloadDialog.invoke(
+ Filename(download.realFilenameOrGuessed),
+ ContentSize(download.contentLength ?: 0),
+ PositiveActionCallback {
+ startDownload(download)
+ useCases.consumeDownload.invoke(tab.id, download.id)
+ },
+ NegativeActionCallback {
+ useCases.cancelDownloadRequest.invoke(tab.id, download.id)
+ },
+ )
+ false
+ }
+ fragmentManager != null && !download.skipConfirmation -> {
+ showDownloadDialog(tab, download)
+ false
+ }
+ else -> {
+ useCases.consumeDownload(tab.id, download.id)
+ startDownload(download)
+ }
}
} else {
onNeedToRequestPermissions(downloadManager.permissions)
@@ -264,25 +339,7 @@ class DownloadsFeature(
) {
appChooserDialog.setApps(apps)
appChooserDialog.onAppSelected = { app ->
- if (app.packageName == applicationContext.packageName) {
- if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
- startDownload(download)
- useCases.consumeDownload(tab.id, download.id)
- } else {
- onNeedToRequestPermissions(downloadManager.permissions)
- }
- } else {
- try {
- applicationContext.startActivity(app.toIntent())
- } catch (error: ActivityNotFoundException) {
- val errorMessage = applicationContext.getString(
- R.string.mozac_feature_downloads_unable_to_open_third_party_app,
- app.name
- )
- Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
- }
- useCases.consumeDownload(tab.id, download.id)
- }
+ onDownloaderAppSelected(app, tab, download)
}
appChooserDialog.onDismiss = {
@@ -294,6 +351,29 @@ class DownloadsFeature(
}
}
+ @VisibleForTesting
+ internal fun onDownloaderAppSelected(app: DownloaderApp, tab: SessionState, download: DownloadState) {
+ if (app.packageName == applicationContext.packageName) {
+ if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
+ startDownload(download)
+ useCases.consumeDownload(tab.id, download.id)
+ } else {
+ onNeedToRequestPermissions(downloadManager.permissions)
+ }
+ } else {
+ try {
+ applicationContext.startActivity(app.toIntent())
+ } catch (error: ActivityNotFoundException) {
+ val errorMessage = applicationContext.getString(
+ R.string.mozac_feature_downloads_unable_to_open_third_party_app,
+ app.name,
+ )
+ Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
+ }
+ useCases.consumeDownload(tab.id, download.id)
+ }
+ }
+
private fun getAppDownloaderDialog() = findPreviousAppDownloaderDialogFragment()
?: DownloadAppChooserDialog.newInstance(
promptsStyling?.gravity,
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ext/DownloadState.kt
=====================================
@@ -47,3 +47,6 @@ internal fun DownloadState.withResponse(headers: Headers, stream: InputStream?):
contentLength = contentLength ?: headers[CONTENT_LENGTH]?.toLongOrNull()
)
}
+
+internal val DownloadState.realFilenameOrGuessed
+ get() = fileName ?: DownloadUtils.guessFileName(null, destinationDirectory, url, contentType)
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ui/DownloaderAppAdapter.kt
=====================================
@@ -16,10 +16,10 @@ import mozilla.components.feature.downloads.R
/**
* An adapter for displaying the applications that can perform downloads.
*/
-internal class DownloaderAppAdapter(
+class DownloaderAppAdapter(
context: Context,
private val apps: List<DownloaderApp>,
- val onAppSelected: ((DownloaderApp) -> Unit)
+ val onAppSelected: ((DownloaderApp) -> Unit),
) : RecyclerView.Adapter<DownloaderAppViewHolder>() {
private val inflater = LayoutInflater.from(context)
@@ -49,11 +49,14 @@ internal class DownloaderAppAdapter(
/**
* View holder for a [DownloaderApp] item.
*/
-internal class DownloaderAppViewHolder(
+class DownloaderAppViewHolder(
itemView: View,
val nameLabel: TextView,
- val iconImage: ImageView
+ val iconImage: ImageView,
) : RecyclerView.ViewHolder(itemView) {
+ /**
+ * Show a certain downloader application in the current View.
+ */
fun bind(app: DownloaderApp, onAppSelected: ((DownloaderApp) -> Unit)) {
itemView.app = app
itemView.setOnClickListener {
=====================================
components/feature/prompts/build.gradle
=====================================
@@ -31,6 +31,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
dependencies {
implementation project(':browser-state')
implementation project(':concept-engine')
+ implementation project(':feature-session')
implementation project(':lib-state')
implementation project(':support-ktx')
implementation project(':support-utils')
@@ -46,6 +47,7 @@ dependencies {
testImplementation Dependencies.testing_coroutines
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
+ testImplementation project(':feature-session')
testImplementation project(':support-test')
testImplementation project(':support-test-libstate')
=====================================
components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
=====================================
@@ -71,6 +71,8 @@ import mozilla.components.feature.prompts.login.LoginExceptions
import mozilla.components.feature.prompts.login.LoginPicker
import mozilla.components.feature.prompts.share.DefaultShareDelegate
import mozilla.components.feature.prompts.share.ShareDelegate
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.session.SessionUseCases.ExitFullScreenUseCase
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.LifecycleAwareFeature
@@ -111,6 +113,7 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
* @property fragmentManager The [FragmentManager] to be used when displaying
* a dialog (fragment).
* @property shareDelegate Delegate used to display share sheet.
+ * @property exitFullscreenUsecase Usecase allowing to exit browser tabs' fullscreen mode.
* @property loginStorageDelegate Delegate used to access login storage. If null,
* 'save login'prompts will not be shown.
* @property isSaveLoginEnabled A callback invoked when a login prompt is triggered. If false,
@@ -144,6 +147,7 @@ class PromptFeature private constructor(
private var customTabId: String?,
private val fragmentManager: FragmentManager,
private val shareDelegate: ShareDelegate,
+ private val exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
override val creditCardValidationDelegate: CreditCardValidationDelegate? = null,
override val loginValidationDelegate: LoginValidationDelegate? = null,
private val isSaveLoginEnabled: () -> Boolean = { false },
@@ -184,6 +188,7 @@ class PromptFeature private constructor(
customTabId: String? = null,
fragmentManager: FragmentManager,
shareDelegate: ShareDelegate = DefaultShareDelegate(),
+ exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
creditCardValidationDelegate: CreditCardValidationDelegate? = null,
loginValidationDelegate: LoginValidationDelegate? = null,
isSaveLoginEnabled: () -> Boolean = { false },
@@ -202,6 +207,7 @@ class PromptFeature private constructor(
customTabId = customTabId,
fragmentManager = fragmentManager,
shareDelegate = shareDelegate,
+ exitFullscreenUsecase = exitFullscreenUsecase,
creditCardValidationDelegate = creditCardValidationDelegate,
loginValidationDelegate = loginValidationDelegate,
isSaveLoginEnabled = isSaveLoginEnabled,
@@ -222,6 +228,7 @@ class PromptFeature private constructor(
customTabId: String? = null,
fragmentManager: FragmentManager,
shareDelegate: ShareDelegate = DefaultShareDelegate(),
+ exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
creditCardValidationDelegate: CreditCardValidationDelegate? = null,
loginValidationDelegate: LoginValidationDelegate? = null,
isSaveLoginEnabled: () -> Boolean = { false },
@@ -240,6 +247,7 @@ class PromptFeature private constructor(
customTabId = customTabId,
fragmentManager = fragmentManager,
shareDelegate = shareDelegate,
+ exitFullscreenUsecase = exitFullscreenUsecase,
creditCardValidationDelegate = creditCardValidationDelegate,
loginValidationDelegate = loginValidationDelegate,
isSaveLoginEnabled = isSaveLoginEnabled,
@@ -420,6 +428,10 @@ class PromptFeature private constructor(
internal fun onPromptRequested(session: SessionState) {
// Some requests are handle with intents
session.content.promptRequests.lastOrNull()?.let { promptRequest ->
+ store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.let {
+ exitFullscreenUsecase(it.id)
+ }
+
when (promptRequest) {
is File -> filePicker.handleFileRequest(promptRequest)
is Share -> handleShareRequest(promptRequest, session)
View it on GitLab: https://gitlab.torproject.org/tpo/applications/android-components/-/compare…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/android-components/-/compare…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/android-components][android-components-102.0.14-12.0-1] 4 commits: Bug 1783561 - Stop BrowserToolbarBehavior positioning the snackbar
by Richard Pospesel (@richard) 16 Mar '23
by Richard Pospesel (@richard) 16 Mar '23
16 Mar '23
Richard Pospesel pushed to branch android-components-102.0.14-12.0-1 at The Tor Project / Applications / android-components
Commits:
8c3c4617 by Mugurell at 2023-03-15T18:01:20+00:00
Bug 1783561 - Stop BrowserToolbarBehavior positioning the snackbar
The snackbar is placed depending on the toolbar so the relation should be
reversed. The toolbar should not know about and control the snackbar.
There could be other siblings that the snackbar wants to position itself by
and so removing this responsability from BrowserToolbarBehavior will help
better support current and future flows.
Backport of https://github.com/mozilla-mobile/firefox-android/commit/04a464e66a95892d9a…
- - - - -
e135dc89 by Mugurell at 2023-03-15T18:01:54+00:00
Bug 1812518 - Allow a custom View for first party downloads
This will allow clients easily implement their own UI.
Backport of https://github.com/mozilla-mobile/firefox-android/commit/e678610bc10df96b85…
- - - - -
c5c27b90 by Mugurell at 2023-03-15T18:01:54+00:00
Bug 1783561 - Allow a custom View for 3rd party downloads
This will allow clients easily implement their own UI
Backport of https://github.com/mozilla-mobile/firefox-android/commit/c53dd65718579006e1…
- - - - -
952ef13c by Mugurell at 2023-03-15T18:01:54+00:00
Improve prompts UX
Backport of https://github.com/mozilla-mobile/firefox-android/commit/1dc21a3786506200be…
- - - - -
8 changed files:
- components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehavior.kt
- components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehaviorTest.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadDialogFragment.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ext/DownloadState.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ui/DownloaderAppAdapter.kt
- components/feature/prompts/build.gradle
- components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
Changes:
=====================================
components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehavior.kt
=====================================
@@ -6,19 +6,15 @@ package mozilla.components.browser.toolbar.behavior
import android.content.Context
import android.util.AttributeSet
-import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
-import com.google.android.material.snackbar.Snackbar
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.concept.engine.EngineView
import mozilla.components.support.ktx.android.view.findViewInHierarchy
-private const val SMALL_ELEVATION_CHANGE = 0.01f
-
/**
* Where the toolbar is placed on the screen.
*/
@@ -35,7 +31,6 @@ enum class ToolbarPosition {
*
* This implementation will:
* - Show/Hide the [BrowserToolbar] automatically when scrolling vertically.
- * - On showing a [Snackbar] position it above the [BrowserToolbar].
* - Snap the [BrowserToolbar] to be hidden or visible when the user stops scrolling.
*/
class BrowserToolbarBehavior(
@@ -128,14 +123,6 @@ class BrowserToolbarBehavior(
return false // allow events to be passed to below listeners
}
- override fun layoutDependsOn(parent: CoordinatorLayout, child: BrowserToolbar, dependency: View): Boolean {
- if (toolbarPosition == ToolbarPosition.BOTTOM && dependency is Snackbar.SnackbarLayout) {
- positionSnackbar(child, dependency)
- }
-
- return super.layoutDependsOn(parent, child, dependency)
- }
-
override fun onLayoutChild(
parent: CoordinatorLayout,
child: BrowserToolbar,
@@ -179,23 +166,6 @@ class BrowserToolbarBehavior(
isScrollEnabled = false
}
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- internal fun positionSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
- val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams
-
- // Position the snackbar above the toolbar so that it doesn't overlay the toolbar.
- params.anchorId = child.id
- params.anchorGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
- params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
-
- snackbarLayout.layoutParams = params
-
- // In order to avoid the snackbar casting a shadow on the toolbar we adjust the elevation of the snackbar here.
- // We still place it slightly behind the toolbar so that it will not animate over the toolbar but instead pop
- // out from under the toolbar.
- snackbarLayout.elevation = child.elevation - SMALL_ELEVATION_CHANGE
- }
-
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun tryToScrollVertically(distance: Float) {
browserToolbar?.let { toolbar ->
=====================================
components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehaviorTest.kt
=====================================
@@ -6,14 +6,12 @@ package mozilla.components.browser.toolbar.behavior
import android.content.Context
import android.graphics.Bitmap
-import android.view.Gravity
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_MOVE
import android.widget.FrameLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.android.material.snackbar.Snackbar
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
@@ -474,29 +472,6 @@ class BrowserToolbarBehaviorTest {
verify(yTranslator).collapseWithAnimation(toolbar)
}
- @Test
- fun `Behavior will position snackbar above toolbar`() {
- val behavior = BrowserToolbarBehavior(testContext, null, ToolbarPosition.BOTTOM)
-
- val toolbar: BrowserToolbar = mock()
- doReturn(4223).`when`(toolbar).id
-
- val layoutParams: CoordinatorLayout.LayoutParams = CoordinatorLayout.LayoutParams(0, 0)
-
- val snackbarLayout: Snackbar.SnackbarLayout = mock()
- doReturn(layoutParams).`when`(snackbarLayout).layoutParams
-
- behavior.layoutDependsOn(
- parent = mock(),
- child = toolbar,
- dependency = snackbarLayout
- )
-
- assertEquals(4223, layoutParams.anchorId)
- assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, layoutParams.anchorGravity)
- assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, layoutParams.gravity)
- }
-
@Test
fun `Behavior will forceExpand when scrolling up and !shouldScroll if the touch was handled in the browser`() {
val behavior = spy(BrowserToolbarBehavior(testContext, null, ToolbarPosition.BOTTOM))
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadDialogFragment.kt
=====================================
@@ -10,7 +10,7 @@ import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.BYTES_TO_MB_LIMIT
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.KILOBYTE
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.MEGABYTE
-import mozilla.components.support.utils.DownloadUtils
+import mozilla.components.feature.downloads.ext.realFilenameOrGuessed
/**
* This is a general representation of a dialog meant to be used in collaboration with [DownloadsFeature]
@@ -34,11 +34,7 @@ abstract class DownloadDialogFragment : AppCompatDialogFragment() {
*/
fun setDownload(download: DownloadState) {
val args = arguments ?: Bundle()
- args.putString(
- KEY_FILE_NAME,
- download.fileName
- ?: DownloadUtils.guessFileName(null, download.destinationDirectory, download.url, download.contentType)
- )
+ args.putString(KEY_FILE_NAME, download.realFilenameOrGuessed)
args.putString(KEY_URL, download.url)
args.putLong(KEY_CONTENT_LENGTH, download.contentLength ?: 0)
arguments = args
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
=====================================
@@ -25,6 +25,7 @@ import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.FRAGMENT_TAG
+import mozilla.components.feature.downloads.ext.realFilenameOrGuessed
import mozilla.components.feature.downloads.manager.DownloadManager
import mozilla.components.feature.downloads.manager.noop
import mozilla.components.feature.downloads.manager.onDownloadStopped
@@ -41,6 +42,43 @@ import mozilla.components.support.ktx.kotlin.isSameOriginAs
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.support.utils.Browsers
+/**
+ * The name of the file to be downloaded.
+ */
+@JvmInline
+value class Filename(val value: String)
+
+/**
+ * The size of the file to be downloaded expressed as the number of `bytes`.
+ * The value will be `0` if the size is unknown.
+ */
+@JvmInline
+value class ContentSize(val value: Long)
+
+/**
+ * The list of all applications that can perform a download, including this application.
+ */
+@JvmInline
+value class ThirdPartyDownloaderApps(val value: List<DownloaderApp>)
+
+/**
+ * Callback for when the user picked a certain application with which to download the current file.
+ */
+@JvmInline
+value class ThirdPartyDownloaderAppChosenCallback(val value: (DownloaderApp) -> Unit)
+
+/**
+ * Callback for when the positive button of a download dialog was tapped.
+ */
+@JvmInline
+value class PositiveActionCallback(val value: () -> Unit)
+
+/**
+ * Callback for when the negative button of a download dialog was tapped.
+ */
+@JvmInline
+value class NegativeActionCallback(val value: () -> Unit)
+
/**
* Feature implementation to provide download functionality for the selected
* session. The feature will subscribe to the selected session and listen
@@ -60,6 +98,10 @@ import mozilla.components.support.utils.Browsers
* @property promptsStyling styling properties for the dialog.
* @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
* if there are multiple apps a chooser dialog will shown.
+ * @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
+ * that will be processed by the current application.
+ * @property customThirdPartyDownloadDialog An optional delegate for showing a dialog for a download
+ * that can be processed by multiple installed applications including the current one.
*/
@Suppress("LongParameterList", "LargeClass")
class DownloadsFeature(
@@ -73,7 +115,11 @@ class DownloadsFeature(
private val tabId: String? = null,
private val fragmentManager: FragmentManager? = null,
private val promptsStyling: PromptsStyling? = null,
- private val shouldForwardToThirdParties: () -> Boolean = { false }
+ private val shouldForwardToThirdParties: () -> Boolean = { false },
+ private val customFirstPartyDownloadDialog:
+ ((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
+ private val customThirdPartyDownloadDialog:
+ ((ThirdPartyDownloaderApps, ThirdPartyDownloaderAppChosenCallback, NegativeActionCallback) -> Unit)? = null,
) : LifecycleAwareFeature, PermissionsFeature {
var onDownloadStopped: onDownloadStopped
@@ -159,16 +205,45 @@ class DownloadsFeature(
val shouldShowAppDownloaderDialog = shouldForwardToThirdParties() && apps.size > 1
return if (shouldShowAppDownloaderDialog) {
- showAppDownloaderDialog(tab, download, apps)
+ when (customThirdPartyDownloadDialog) {
+ null -> showAppDownloaderDialog(tab, download, apps)
+ else -> customThirdPartyDownloadDialog.invoke(
+ ThirdPartyDownloaderApps(apps),
+ ThirdPartyDownloaderAppChosenCallback {
+ onDownloaderAppSelected(it, tab, download)
+ },
+ NegativeActionCallback {
+ useCases.cancelDownloadRequest.invoke(tab.id, download.id)
+ },
+ )
+ }
+
false
} else {
if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
- if (fragmentManager != null && !download.skipConfirmation) {
- showDownloadDialog(tab, download)
- false
- } else {
- useCases.consumeDownload(tab.id, download.id)
- startDownload(download)
+ when {
+ customFirstPartyDownloadDialog != null && !download.skipConfirmation -> {
+ customFirstPartyDownloadDialog.invoke(
+ Filename(download.realFilenameOrGuessed),
+ ContentSize(download.contentLength ?: 0),
+ PositiveActionCallback {
+ startDownload(download)
+ useCases.consumeDownload.invoke(tab.id, download.id)
+ },
+ NegativeActionCallback {
+ useCases.cancelDownloadRequest.invoke(tab.id, download.id)
+ },
+ )
+ false
+ }
+ fragmentManager != null && !download.skipConfirmation -> {
+ showDownloadDialog(tab, download)
+ false
+ }
+ else -> {
+ useCases.consumeDownload(tab.id, download.id)
+ startDownload(download)
+ }
}
} else {
onNeedToRequestPermissions(downloadManager.permissions)
@@ -264,25 +339,7 @@ class DownloadsFeature(
) {
appChooserDialog.setApps(apps)
appChooserDialog.onAppSelected = { app ->
- if (app.packageName == applicationContext.packageName) {
- if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
- startDownload(download)
- useCases.consumeDownload(tab.id, download.id)
- } else {
- onNeedToRequestPermissions(downloadManager.permissions)
- }
- } else {
- try {
- applicationContext.startActivity(app.toIntent())
- } catch (error: ActivityNotFoundException) {
- val errorMessage = applicationContext.getString(
- R.string.mozac_feature_downloads_unable_to_open_third_party_app,
- app.name
- )
- Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
- }
- useCases.consumeDownload(tab.id, download.id)
- }
+ onDownloaderAppSelected(app, tab, download)
}
appChooserDialog.onDismiss = {
@@ -294,6 +351,29 @@ class DownloadsFeature(
}
}
+ @VisibleForTesting
+ internal fun onDownloaderAppSelected(app: DownloaderApp, tab: SessionState, download: DownloadState) {
+ if (app.packageName == applicationContext.packageName) {
+ if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
+ startDownload(download)
+ useCases.consumeDownload(tab.id, download.id)
+ } else {
+ onNeedToRequestPermissions(downloadManager.permissions)
+ }
+ } else {
+ try {
+ applicationContext.startActivity(app.toIntent())
+ } catch (error: ActivityNotFoundException) {
+ val errorMessage = applicationContext.getString(
+ R.string.mozac_feature_downloads_unable_to_open_third_party_app,
+ app.name,
+ )
+ Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
+ }
+ useCases.consumeDownload(tab.id, download.id)
+ }
+ }
+
private fun getAppDownloaderDialog() = findPreviousAppDownloaderDialogFragment()
?: DownloadAppChooserDialog.newInstance(
promptsStyling?.gravity,
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ext/DownloadState.kt
=====================================
@@ -47,3 +47,6 @@ internal fun DownloadState.withResponse(headers: Headers, stream: InputStream?):
contentLength = contentLength ?: headers[CONTENT_LENGTH]?.toLongOrNull()
)
}
+
+internal val DownloadState.realFilenameOrGuessed
+ get() = fileName ?: DownloadUtils.guessFileName(null, destinationDirectory, url, contentType)
=====================================
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ui/DownloaderAppAdapter.kt
=====================================
@@ -16,10 +16,10 @@ import mozilla.components.feature.downloads.R
/**
* An adapter for displaying the applications that can perform downloads.
*/
-internal class DownloaderAppAdapter(
+class DownloaderAppAdapter(
context: Context,
private val apps: List<DownloaderApp>,
- val onAppSelected: ((DownloaderApp) -> Unit)
+ val onAppSelected: ((DownloaderApp) -> Unit),
) : RecyclerView.Adapter<DownloaderAppViewHolder>() {
private val inflater = LayoutInflater.from(context)
@@ -49,11 +49,14 @@ internal class DownloaderAppAdapter(
/**
* View holder for a [DownloaderApp] item.
*/
-internal class DownloaderAppViewHolder(
+class DownloaderAppViewHolder(
itemView: View,
val nameLabel: TextView,
- val iconImage: ImageView
+ val iconImage: ImageView,
) : RecyclerView.ViewHolder(itemView) {
+ /**
+ * Show a certain downloader application in the current View.
+ */
fun bind(app: DownloaderApp, onAppSelected: ((DownloaderApp) -> Unit)) {
itemView.app = app
itemView.setOnClickListener {
=====================================
components/feature/prompts/build.gradle
=====================================
@@ -31,6 +31,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
dependencies {
implementation project(':browser-state')
implementation project(':concept-engine')
+ implementation project(':feature-session')
implementation project(':lib-state')
implementation project(':support-ktx')
implementation project(':support-utils')
@@ -46,6 +47,7 @@ dependencies {
testImplementation Dependencies.testing_coroutines
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
+ testImplementation project(':feature-session')
testImplementation project(':support-test')
testImplementation project(':support-test-libstate')
=====================================
components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
=====================================
@@ -71,6 +71,8 @@ import mozilla.components.feature.prompts.login.LoginExceptions
import mozilla.components.feature.prompts.login.LoginPicker
import mozilla.components.feature.prompts.share.DefaultShareDelegate
import mozilla.components.feature.prompts.share.ShareDelegate
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.session.SessionUseCases.ExitFullScreenUseCase
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.LifecycleAwareFeature
@@ -111,6 +113,7 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
* @property fragmentManager The [FragmentManager] to be used when displaying
* a dialog (fragment).
* @property shareDelegate Delegate used to display share sheet.
+ * @property exitFullscreenUsecase Usecase allowing to exit browser tabs' fullscreen mode.
* @property loginStorageDelegate Delegate used to access login storage. If null,
* 'save login'prompts will not be shown.
* @property isSaveLoginEnabled A callback invoked when a login prompt is triggered. If false,
@@ -144,6 +147,7 @@ class PromptFeature private constructor(
private var customTabId: String?,
private val fragmentManager: FragmentManager,
private val shareDelegate: ShareDelegate,
+ private val exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
override val creditCardValidationDelegate: CreditCardValidationDelegate? = null,
override val loginValidationDelegate: LoginValidationDelegate? = null,
private val isSaveLoginEnabled: () -> Boolean = { false },
@@ -184,6 +188,7 @@ class PromptFeature private constructor(
customTabId: String? = null,
fragmentManager: FragmentManager,
shareDelegate: ShareDelegate = DefaultShareDelegate(),
+ exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
creditCardValidationDelegate: CreditCardValidationDelegate? = null,
loginValidationDelegate: LoginValidationDelegate? = null,
isSaveLoginEnabled: () -> Boolean = { false },
@@ -202,6 +207,7 @@ class PromptFeature private constructor(
customTabId = customTabId,
fragmentManager = fragmentManager,
shareDelegate = shareDelegate,
+ exitFullscreenUsecase = exitFullscreenUsecase,
creditCardValidationDelegate = creditCardValidationDelegate,
loginValidationDelegate = loginValidationDelegate,
isSaveLoginEnabled = isSaveLoginEnabled,
@@ -222,6 +228,7 @@ class PromptFeature private constructor(
customTabId: String? = null,
fragmentManager: FragmentManager,
shareDelegate: ShareDelegate = DefaultShareDelegate(),
+ exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
creditCardValidationDelegate: CreditCardValidationDelegate? = null,
loginValidationDelegate: LoginValidationDelegate? = null,
isSaveLoginEnabled: () -> Boolean = { false },
@@ -240,6 +247,7 @@ class PromptFeature private constructor(
customTabId = customTabId,
fragmentManager = fragmentManager,
shareDelegate = shareDelegate,
+ exitFullscreenUsecase = exitFullscreenUsecase,
creditCardValidationDelegate = creditCardValidationDelegate,
loginValidationDelegate = loginValidationDelegate,
isSaveLoginEnabled = isSaveLoginEnabled,
@@ -420,6 +428,10 @@ class PromptFeature private constructor(
internal fun onPromptRequested(session: SessionState) {
// Some requests are handle with intents
session.content.promptRequests.lastOrNull()?.let { promptRequest ->
+ store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.let {
+ exitFullscreenUsecase(it.id)
+ }
+
when (promptRequest) {
is File -> filePicker.handleFileRequest(promptRequest)
is Share -> handleShareRequest(promptRequest, session)
View it on GitLab: https://gitlab.torproject.org/tpo/applications/android-components/-/compare…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/android-components/-/compare…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-102.9.0esr-12.5-1] fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
by Pier Angelo Vendrame (@pierov) 15 Mar '23
by Pier Angelo Vendrame (@pierov) 15 Mar '23
15 Mar '23
Pier Angelo Vendrame pushed to branch tor-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
d41e60a2 by Dan Ballard at 2023-03-15T23:04:02+01:00
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
bug 41526: make tor connection cancel button grey
- - - - -
2 changed files:
- browser/components/torconnect/content/aboutTorConnect.css
- browser/components/torconnect/content/aboutTorConnect.js
Changes:
=====================================
browser/components/torconnect/content/aboutTorConnect.css
=====================================
@@ -136,6 +136,13 @@ button {
fill: white;
}
+#cancelButton {
+ color: var(--in-content-button-text-color);
+ border: 1px solid var(--in-content-button-border-color);
+ border-radius: 4px;
+ background-color: var(--in-content-button-background);
+}
+
#locationDropdownLabel {
margin-block: auto;
margin-inline: 4px;
=====================================
browser/components/torconnect/content/aboutTorConnect.js
=====================================
@@ -517,9 +517,6 @@ class AboutTorConnect {
this.hide(this.elements.viewLogButton);
}
this.show(this.elements.cancelButton, true);
- if (state.StateChanged) {
- this.elements.cancelButton.focus();
- }
}
showOffline(error) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/d41e60a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/d41e60a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0