tor-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
March 2023
- 3 participants
- 142 discussions

[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