Bug 1729837: Prompt users on macOS to launch an existing installation from the Applications directory, rather than a .dmg disk image, when one is found. r=mstange,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D125015
This commit is contained in:
@@ -16,3 +16,12 @@ prompt-to-install-no-button = Don’t Install
|
||||
|
||||
install-failed-title = { -brand-short-name } installation failed.
|
||||
install-failed-message = { -brand-short-name } failed to install but will continue to run.
|
||||
|
||||
## Strings for a dialog that recommends to the user to start an existing
|
||||
## installation of the app in the Applications directory if one is detected,
|
||||
## rather than the app that was double-clicked in a .dmg.
|
||||
|
||||
prompt-to-launch-existing-app-title = Open existing { -brand-short-name } application?
|
||||
prompt-to-launch-existing-app-message = You already have { -brand-short-name } installed. Use the installed application to stay up to date and prevent data loss.
|
||||
prompt-to-launch-existing-app-yes-button = Open existing
|
||||
prompt-to-launch-existing-app-no-button = No thanks
|
||||
|
||||
@@ -38,6 +38,81 @@
|
||||
namespace mozilla {
|
||||
namespace MacRunFromDmgUtils {
|
||||
|
||||
/**
|
||||
* Opens a dialog to ask the user whether the existing app in the Applications
|
||||
* folder should be launched, or if the user wants to proceed with launching
|
||||
* the app from the .dmg.
|
||||
* Returns true if the dialog is successfully opened and the user chooses to
|
||||
* launch the app from the Applications folder, otherwise returns false.
|
||||
*/
|
||||
static bool AskUserIfWeShouldLaunchExistingInstall() {
|
||||
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
|
||||
|
||||
// Try to get the localized strings:
|
||||
nsTArray<nsCString> resIds = {
|
||||
"branding/brand.ftl"_ns,
|
||||
"toolkit/global/run-from-dmg.ftl"_ns,
|
||||
};
|
||||
RefPtr<intl::Localization> l10n = intl::Localization::Create(resIds, true);
|
||||
|
||||
ErrorResult rv;
|
||||
nsAutoCString mozTitle, mozMessage, mozLaunchExisting, mozLaunchFromDMG;
|
||||
l10n->FormatValueSync("prompt-to-launch-existing-app-title"_ns, {}, mozTitle, rv);
|
||||
if (rv.Failed()) {
|
||||
return false;
|
||||
}
|
||||
l10n->FormatValueSync("prompt-to-launch-existing-app-message"_ns, {}, mozMessage, rv);
|
||||
if (rv.Failed()) {
|
||||
return false;
|
||||
}
|
||||
l10n->FormatValueSync("prompt-to-launch-existing-app-yes-button"_ns, {}, mozLaunchExisting, rv);
|
||||
if (rv.Failed()) {
|
||||
return false;
|
||||
}
|
||||
l10n->FormatValueSync("prompt-to-launch-existing-app-no-button"_ns, {}, mozLaunchFromDMG, rv);
|
||||
if (rv.Failed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NSString* title = [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozTitle.get())];
|
||||
NSString* message =
|
||||
[NSString stringWithUTF8String:reinterpret_cast<const char*>(mozMessage.get())];
|
||||
NSString* launchExisting =
|
||||
[NSString stringWithUTF8String:reinterpret_cast<const char*>(mozLaunchExisting.get())];
|
||||
NSString* launchFromDMG =
|
||||
[NSString stringWithUTF8String:reinterpret_cast<const char*>(mozLaunchFromDMG.get())];
|
||||
|
||||
NSAlert* alert = [[[NSAlert alloc] init] autorelease];
|
||||
|
||||
// Note that we don't set an icon since the app icon is used by default.
|
||||
[alert setAlertStyle:NSAlertStyleInformational];
|
||||
[alert setMessageText:title];
|
||||
[alert setInformativeText:message];
|
||||
// Note that if the user hits 'Enter' the "Install" button is activated,
|
||||
// whereas if they hit 'Space' the "Don't Install" button is activated.
|
||||
// That's standard behavior so probably desirable.
|
||||
[alert addButtonWithTitle:launchExisting];
|
||||
NSButton* launchFromDMGButton = [alert addButtonWithTitle:launchFromDMG];
|
||||
// Since the "Don't Install" button doesn't have the title "Cancel" we need
|
||||
// to map the Escape key to it manually:
|
||||
[launchFromDMGButton setKeyEquivalent:@"\e"];
|
||||
|
||||
__block NSInteger result = -1;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
result = [alert runModal];
|
||||
[NSApp stop:nil];
|
||||
});
|
||||
|
||||
// We need to call run on NSApp here for accessibility. See
|
||||
// AskUserIfWeShouldInstall for a detailed explanation.
|
||||
[NSApp run];
|
||||
MOZ_ASSERT(result != -1);
|
||||
|
||||
return result == NSAlertFirstButtonReturn;
|
||||
|
||||
NS_OBJC_END_TRY_BLOCK_RETURN(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog to ask the user whether the app should be installed to their
|
||||
* Applications folder. Returns true if the dialog is successfully opened and
|
||||
@@ -158,7 +233,16 @@ static void ShowInstallFailedDialog() {
|
||||
[alert setMessageText:title];
|
||||
[alert setInformativeText:message];
|
||||
|
||||
[alert runModal];
|
||||
__block NSInteger result = -1;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
result = [alert runModal];
|
||||
[NSApp stop:nil];
|
||||
});
|
||||
|
||||
// We need to call run on NSApp here for accessibility. See
|
||||
// AskUserIfWeShouldInstall for a detailed explanation.
|
||||
[NSApp run];
|
||||
MOZ_ASSERT(result != -1);
|
||||
|
||||
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
||||
}
|
||||
@@ -389,15 +473,14 @@ bool MaybeInstallFromDmgAndRelaunch() {
|
||||
NSString* destPath = [applicationsDir stringByAppendingPathComponent:appName];
|
||||
|
||||
// If the app (an app of the same name) is already installed we can't really
|
||||
// tell if we're dealing with the edge case of an inexperienced user running
|
||||
// from .dmg by mistake, or if we're dealing with a more sophisticated user
|
||||
// intentionally running from .dmg.
|
||||
// We could throw a series of prompts at the user to figure out if they want
|
||||
// to overwrite the installed app, or maybe just launch it, or continue with
|
||||
// running from .dmg, but that seems like overkill for an edge case when
|
||||
// we're just trying to provide mitigate inexperienced mac users trying to
|
||||
// get and run our app for the first time.
|
||||
// tell without asking if we're dealing with the edge case of an
|
||||
// inexperienced user running from .dmg by mistake, or if we're dealing with
|
||||
// a more sophisticated user intentionally running from .dmg.
|
||||
if ([fileManager fileExistsAtPath:destPath]) {
|
||||
if (AskUserIfWeShouldLaunchExistingInstall()) {
|
||||
LaunchInstalledApp(destPath);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user