Bug 1781929 - Part 1: Handle toast notifications from background tasks specially. r=nrishel

In background task mode:

1. Re-launch Firefox into the default browsing profile, not the
   (possibly ephemeral) background task profile.
2. Don't show the in-product notification settings, which won't apply
   to the relevant Firefox profile.

Differential Revision: https://phabricator.services.mozilla.com/D155908
This commit is contained in:
Nick Alexander 2022-09-03 22:49:00 +00:00
Родитель b1c91cad45
Коммит c2e7a9b4e8
2 изменённых файлов: 155 добавлений и 40 удалений

Просмотреть файл

@ -11,6 +11,9 @@
#include "imgIContainer.h"
#include "imgIRequest.h"
#include "mozilla/gfx/2D.h"
#ifdef MOZ_BACKGROUNDTASKS
# include "mozilla/BackgroundTasks.h"
#endif
#include "mozilla/HashFunctions.h"
#include "mozilla/Result.h"
#include "mozilla/Tokenizer.h"
@ -21,6 +24,8 @@
#include "nsDirectoryServiceUtils.h"
#include "nsIDUtils.h"
#include "nsIStringBundle.h"
#include "nsIToolkitProfile.h"
#include "nsIToolkitProfileService.h"
#include "nsIURI.h"
#include "nsIWidget.h"
#include "nsIWindowMediator.h"
@ -171,13 +176,42 @@ static Result<nsString, nsresult> GetLaunchArgument() {
// `program` argument.
launchArg += u"program\n"_ns MOZ_APP_NAME;
// `profile` argument
// `profile` argument.
nsCOMPtr<nsIFile> profDir;
MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(profDir)));
nsAutoString profilePath;
MOZ_TRY(profDir->GetPath(profilePath));
launchArg += u"\nprofile\n"_ns + profilePath;
bool wantCurrentProfile = true;
#ifdef MOZ_BACKGROUNDTASKS
if (BackgroundTasks::IsBackgroundTaskMode()) {
// Notifications popped from a background task want to invoke Firefox with a
// different profile -- the default browsing profile. We'd prefer to not
// specify a profile, so that the Firefox invoked by the notification server
// chooses its default profile, but this might pop the profile chooser in
// some configurations.
wantCurrentProfile = false;
nsCOMPtr<nsIToolkitProfileService> profileSvc =
do_GetService(NS_PROFILESERVICE_CONTRACTID);
if (profileSvc) {
nsCOMPtr<nsIToolkitProfile> defaultProfile;
nsresult rv =
profileSvc->GetDefaultProfile(getter_AddRefs(defaultProfile));
if (NS_SUCCEEDED(rv) && defaultProfile) {
// Not all installations have a default profile. But if one is set,
// then it should have a profile directory.
MOZ_TRY(defaultProfile->GetRootDir(getter_AddRefs(profDir)));
}
}
}
#endif
if (wantCurrentProfile) {
MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(profDir)));
}
if (profDir) {
nsAutoString profilePath;
MOZ_TRY(profDir->GetPath(profilePath));
launchArg += u"\nprofile\n"_ns + profilePath;
}
return launchArg;
}
@ -426,11 +460,23 @@ ComPtr<IXmlDocument> ToastNotificationHandler::CreateToastXmlDocument() {
u"snooze"_ns, u"contextmenu"_ns);
}
nsAutoString settingsButtonTitle;
bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
success = AddActionNode(toastXml, actionsNode, settingsButtonTitle, launchArg,
u"settings"_ns, u"contextmenu"_ns);
NS_ENSURE_TRUE(success, nullptr);
bool wantSettings = true;
#ifdef MOZ_BACKGROUNDTASKS
if (BackgroundTasks::IsBackgroundTaskMode()) {
// Notifications popped from a background task want to invoke Firefox with a
// different profile -- the default browsing profile. Don't link to Firefox
// settings in some different profile: the relevant Firefox settings won't
// take effect.
wantSettings = false;
}
#endif
if (MOZ_LIKELY(wantSettings)) {
nsAutoString settingsButtonTitle;
bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
success = AddActionNode(toastXml, actionsNode, settingsButtonTitle,
launchArg, u"settings"_ns, u"contextmenu"_ns);
NS_ENSURE_TRUE(success, nullptr);
}
for (const auto& action : mActions) {
// Bug 1778596: include per-action icon from image URL.

Просмотреть файл

@ -56,21 +56,30 @@ function makeAlert(options) {
return alert;
}
function testAlert(serverEnabled) {
function testAlert(when, { serverEnabled, profD, isBackgroundTaskMode } = {}) {
let argumentString = argument => {
// &#xA; is "\n".
let s = ``;
if (serverEnabled) {
s += `program&#xA;${AppConstants.MOZ_APP_NAME}&#xA;profile&#xA;${gProfD.path}`;
s += `program&#xA;${AppConstants.MOZ_APP_NAME}`;
} else {
s += `invalid key&#xA;invalid value`;
}
if (serverEnabled && profD) {
s += `&#xA;profile&#xA;${profD.path}`;
}
if (argument) {
s += `&#xA;action&#xA;${argument}`;
}
return s;
};
let settingsAction = isBackgroundTaskMode
? ""
: `<action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/>`;
let alertsService = Cc["@mozilla.org/system-alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIWindowsAlertsService);
@ -85,32 +94,40 @@ function testAlert(serverEnabled) {
];
let alert = makeAlert({ name, title, text });
let expected = `<toast launch="${argumentString()}"><visual><binding template="ToastText03"><text id="1">title</text><text id="2">text</text></binding></visual><actions><action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/></actions></toast>`;
Assert.equal(expected, alertsService.getXmlStringForWindowsAlert(alert));
let expected = `<toast launch="${argumentString()}"><visual><binding template="ToastText03"><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
Assert.equal(
expected.replace("<actions></actions>", "<actions/>"),
alertsService.getXmlStringForWindowsAlert(alert),
when
);
alert = makeAlert({ name, title, text, imageURL });
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions><action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/></actions></toast>`;
Assert.equal(expected, alertsService.getXmlStringForWindowsAlert(alert));
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
Assert.equal(
expected.replace("<actions></actions>", "<actions/>"),
alertsService.getXmlStringForWindowsAlert(alert),
when
);
alert = makeAlert({ name, title, text, imageURL, requireInteraction: true });
expected = `<toast scenario="reminder" launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions><action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/></actions></toast>`;
Assert.equal(expected, alertsService.getXmlStringForWindowsAlert(alert));
expected = `<toast scenario="reminder" launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
Assert.equal(
expected.replace("<actions></actions>", "<actions/>"),
alertsService.getXmlStringForWindowsAlert(alert),
when
);
alert = makeAlert({ name, title, text, imageURL, actions });
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions><action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/><action content="title1" arguments="${argumentString(
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="title1" arguments="${argumentString(
"action1"
)}"/><action content="title2" arguments="${argumentString(
"action2"
)}"/></actions></toast>`;
Assert.equal(expected, alertsService.getXmlStringForWindowsAlert(alert));
Assert.equal(
expected.replace("<actions></actions>", "<actions/>"),
alertsService.getXmlStringForWindowsAlert(alert),
when
);
// Chrome privileged alerts can use `windowsSystemActivationType`.
let systemActions = [
@ -134,10 +151,12 @@ function testAlert(serverEnabled) {
principal: systemPrincipal,
actions: systemActions,
});
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions><action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/><action content="dismissTitle" arguments="dismiss" activationType="system"/><action content="snoozeTitle" arguments="snooze" activationType="system"/></actions></toast>`;
Assert.equal(expected, alertsService.getXmlStringForWindowsAlert(alert));
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="dismissTitle" arguments="dismiss" activationType="system"/><action content="snoozeTitle" arguments="snooze" activationType="system"/></actions></toast>`;
Assert.equal(
expected,
alertsService.getXmlStringForWindowsAlert(alert),
when
);
// But content unprivileged alerts can't use `windowsSystemActivationType`.
let launchUrl = "https://example.com/foo/bar.html";
@ -157,31 +176,81 @@ function testAlert(serverEnabled) {
});
expected = `<toast launch="${argumentString()}"><visual><binding template="ToastImageAndText04"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text><text id="3" placement="attribution">via example.com</text></binding></visual><actions><action content="Disable notifications from example.com" arguments="${argumentString(
"snooze"
)}" placement="contextmenu"/><action content="Notification settings" arguments="${argumentString(
"settings"
)}" placement="contextmenu"/><action content="dismissTitle" arguments="${argumentString(
)}" placement="contextmenu"/>${settingsAction}<action content="dismissTitle" arguments="${argumentString(
"dismiss"
)}"/><action content="snoozeTitle" arguments="${argumentString(
"snooze"
)}"/></actions></toast>`;
Assert.equal(expected, alertsService.getXmlStringForWindowsAlert(alert));
Assert.equal(
expected,
alertsService.getXmlStringForWindowsAlert(alert),
when
);
}
add_task(async () => {
Services.prefs.clearUserPref(
"alerts.useSystemBackend.windows.notificationserver.enabled"
);
testAlert(false);
testAlert("when notification server pref is unset (i.e., default)", {
profD: gProfD,
});
Services.prefs.setBoolPref(
"alerts.useSystemBackend.windows.notificationserver.enabled",
false
);
testAlert(false);
testAlert("when notification server pref is false", { profD: gProfD });
Services.prefs.setBoolPref(
"alerts.useSystemBackend.windows.notificationserver.enabled",
true
);
testAlert(true);
testAlert("when notification server pref is true", {
serverEnabled: true,
profD: gProfD,
});
});
let condition = {
skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
};
add_task(condition, async () => {
const bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
Ci.nsIBackgroundTasks
);
// Pretend that this is a background task.
bts.overrideBackgroundTaskNameForTesting("taskname");
Services.prefs.setBoolPref(
"alerts.useSystemBackend.windows.notificationserver.enabled",
true
);
testAlert(
"when notification server pref is true in background task, no default profile",
{ serverEnabled: true, isBackgroundTaskMode: true }
);
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
Ci.nsIToolkitProfileService
);
let profilePath = do_get_profile();
profilePath.append(`test_windows_alert_service`);
let profile = profileService.createUniqueProfile(
profilePath,
"test_windows_alert_service"
);
profileService.defaultProfile = profile;
testAlert(
"when notification server pref is true in background task, default profile",
{ serverEnabled: true, isBackgroundTaskMode: true, profD: profilePath }
);
// No longer a background task,
bts.overrideBackgroundTaskNameForTesting("");
});