зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1781929 - Part 4: Pass through or launch Firefox from notification server. r=nrishel
When the notification server callback is executed by the Windows notification system, it invokes Firefox with additional command line parameters, most importantly the Windows-specific notification "Windows tag". If no appropriate Firefox is running, the command line will be processed, the provided Windows tag will be inspected (and seen to not be registered with this running Firefox instance) and a "launch URL" stored as part of the Windows notification itself opened (if one is provided). If an appropriate Firefox is running, the remoting protocol will forward this command line to the running instance. If the instance recognizes the provided `--notification-windowsTag`, the command line will be ignored. When the notification server exits, Windows will fallback to the Windows 8.1 style notification callbacks registered for this Windows tag and the existing (non notification server) behaviour will occur. In fact, the server therefore waits for a Windows tag-specific system event to be signalled by the invoked Firefox (or a sibling process). If we were to return `S_OK` from the notification server immediately, and a running Firefox process would handle the notification via Windows 8.1-style notification callbacks, then Windows would fall back to those callbacks. The invoked callbacks unregister themselves upon completion, often before the launched Firefox has an opportunity to process the command line. By waiting for this system event, we allow the invoked Firefox to process the command line while its own notification callbacks are registered and therefore recognize that its callbacks will handle the notification. Differential Revision: https://phabricator.services.mozilla.com/D154468
This commit is contained in:
Родитель
7dff57f69c
Коммит
7ad3c38071
|
@ -1016,6 +1016,37 @@ nsDefaultCommandLineHandler.prototype = {
|
|||
handle: function dch_handle(cmdLine) {
|
||||
var urilist = [];
|
||||
|
||||
if (AppConstants.platform == "win") {
|
||||
let windowsAlertsService = Cc["@mozilla.org/system-alerts-service;1"]
|
||||
.getService(Ci.nsIAlertsService)
|
||||
.QueryInterface(Ci.nsIWindowsAlertsService);
|
||||
var tag;
|
||||
while (
|
||||
(tag = cmdLine.handleFlagWithParam("notification-windowsTag", false))
|
||||
) {
|
||||
let onUnknownWindowsTag = (unknownTag, launchUrl) => {
|
||||
let uri = resolveURIInternal(cmdLine, launchUrl);
|
||||
console.info(
|
||||
`Opening ${uri.spec} to complete Windows notification with tag '${unknownTag}'`
|
||||
);
|
||||
urilist.push(uri);
|
||||
};
|
||||
|
||||
try {
|
||||
if (
|
||||
windowsAlertsService?.handleWindowsTag(tag, onUnknownWindowsTag)
|
||||
) {
|
||||
// Don't pop open a new window.
|
||||
cmdLine.preventDefault = true;
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(
|
||||
`Error handling Windows notification with tag '${tag}': ${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
|
||||
Services.startup.wasSilentlyStarted
|
||||
|
|
|
@ -7,16 +7,44 @@
|
|||
#include "nsISupports.idl"
|
||||
#include "nsIObserver.idl"
|
||||
|
||||
[scriptable, function, uuid(059f8305-4e2f-4d31-a9cb-5b918ee84773)]
|
||||
interface nsIUnknownWindowsTagListener : nsISupports
|
||||
{
|
||||
/**
|
||||
* Handle a launch URL associated to the given Windows-specific tag string.
|
||||
* Usually, this will navigate to the launch URL in some manner.
|
||||
*
|
||||
* @param {AString} aWindowsTag the tag
|
||||
* @param {AString} aLaunchURL associated launch URL.
|
||||
*/
|
||||
void handleUnknownWindowsTag(in AString aWindowsTag,
|
||||
in AString aLaunchURL);
|
||||
};
|
||||
|
||||
[scriptable, uuid(e01c8066-fb4b-4304-b9c9-ab6ed4a8322c)]
|
||||
interface nsIWindowsAlertsService : nsIAlertsService
|
||||
{
|
||||
/**
|
||||
* If callbacks for the given Windows-specific tag string will be handled by
|
||||
* this Firefox process, set the associated event.
|
||||
*
|
||||
* @param {AString} aWindowsTag the tag
|
||||
* @param {nsIUnhandledWindowsTagListener} aListener the listener to callback
|
||||
* if the tag is unknown and has an associated launch URL.
|
||||
* @return {boolean} `true` iff the tag is registered and an event was set.
|
||||
*/
|
||||
bool handleWindowsTag(in AString aWindowsTag,
|
||||
in nsIUnknownWindowsTagListener aListener);
|
||||
|
||||
/**
|
||||
* Get the Windows-specific XML generated for the given alert.
|
||||
*
|
||||
* @note This method is intended for testing purposes.
|
||||
*
|
||||
* @param {nsIAlertNotification} aAlert the alert
|
||||
* @param {AString} an optional Windows tag; default is generated
|
||||
* @return {string} generated XML
|
||||
*/
|
||||
AString getXmlStringForWindowsAlert(in nsIAlertNotification aAlert);
|
||||
AString getXmlStringForWindowsAlert(in nsIAlertNotification aAlert,
|
||||
[optional] in AString aWindowsTag);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// XUL notifications make no sense in background tasks. This is only applies to
|
||||
// Windows for now.
|
||||
pref("alerts.useSystemBackend", true);
|
||||
pref("alerts.useSystemBackend.windows.notificationserver.enabled", true);
|
||||
|
||||
// Configure Messaging Experiments for background tasks, with
|
||||
// background task-specific feature ID. The regular Firefox Desktop
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#define NOTIFICATION_SERVER_EVENT_TIMEOUT_MS (10 * 1000)
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
NotificationCallback::QueryInterface(REFIID riid, void** ppvObject) {
|
||||
if (!ppvObject) {
|
||||
|
@ -39,6 +41,7 @@ HRESULT STDMETHODCALLTYPE NotificationCallback::Activate(
|
|||
const NOTIFICATION_USER_INPUT_DATA* data, ULONG dataCount) {
|
||||
std::wstring program;
|
||||
std::wstring profile;
|
||||
std::wstring windowsTag;
|
||||
|
||||
LOG_ERROR_MESSAGE((L"Invoked with arguments: '%s'"), invokedArgs);
|
||||
|
||||
|
@ -52,6 +55,8 @@ HRESULT STDMETHODCALLTYPE NotificationCallback::Activate(
|
|||
}
|
||||
} else if (key == L"profile") {
|
||||
profile = value;
|
||||
} else if (key == L"windowsTag") {
|
||||
windowsTag = value;
|
||||
} else if (key == L"action") {
|
||||
// Remainder of args are from the Web Notification action, don't parse.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1781929.
|
||||
|
@ -77,9 +82,24 @@ HRESULT STDMETHODCALLTYPE NotificationCallback::Activate(
|
|||
LOG_ERROR_MESSAGE((L"No profile; invocation will choose default profile"));
|
||||
}
|
||||
|
||||
if (!windowsTag.empty()) {
|
||||
childArgv.push_back(L"--notification-windowsTag");
|
||||
childArgv.push_back(windowsTag.c_str());
|
||||
} else {
|
||||
LOG_ERROR_MESSAGE((L"No windowsTag; invoking anyway"));
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<wchar_t[]> cmdLine(
|
||||
mozilla::MakeCommandLine(childArgv.size(), childArgv.data()));
|
||||
|
||||
// This event object will let Firefox notify us when it has handled the
|
||||
// notification.
|
||||
std::wstring eventName(windowsTag);
|
||||
nsAutoHandle event;
|
||||
if (!eventName.empty()) {
|
||||
event.own(CreateEventW(nullptr, TRUE, FALSE, eventName.c_str()));
|
||||
}
|
||||
|
||||
STARTUPINFOW si = {0};
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
@ -91,5 +111,32 @@ HRESULT STDMETHODCALLTYPE NotificationCallback::Activate(
|
|||
|
||||
LOG_ERROR_MESSAGE((L"Invoked %s"), cmdLine.get());
|
||||
|
||||
if (windowsTag.empty()) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (event.get()) {
|
||||
LOG_ERROR_MESSAGE((L"Waiting on event with name '%s'"), eventName.c_str());
|
||||
|
||||
DWORD result =
|
||||
WaitForSingleObject(event, NOTIFICATION_SERVER_EVENT_TIMEOUT_MS);
|
||||
if (result == WAIT_TIMEOUT) {
|
||||
LOG_ERROR_MESSAGE(L"Wait timed out");
|
||||
return S_OK;
|
||||
} else if (result == WAIT_FAILED) {
|
||||
LOG_ERROR_MESSAGE((L"Wait failed: %#X"), GetLastError());
|
||||
return S_OK;
|
||||
} else if (result == WAIT_ABANDONED) {
|
||||
LOG_ERROR_MESSAGE((L"Wait abandoned"));
|
||||
return S_OK;
|
||||
} else {
|
||||
LOG_ERROR_MESSAGE((L"Wait succeeded!"));
|
||||
return S_OK;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR_MESSAGE((L"Failed to create event with name '%s'"),
|
||||
eventName.c_str());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
||||
static LazyLogModule sWASLog("WindowsAlertsService");
|
||||
LazyLogModule sWASLog("WindowsAlertsService");
|
||||
|
||||
NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIWindowsAlertsService,
|
||||
nsIAlertsDoNotDisturb, nsIObserver)
|
||||
|
@ -431,8 +431,16 @@ ToastNotification::ShowAlert(nsIAlertNotification* aAlert,
|
|||
textClickable, requireInteraction, actions, isSystemPrincipal, launchUrl);
|
||||
mActiveHandlers.InsertOrUpdate(name, RefPtr{handler});
|
||||
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Adding handler '%s': [%p] (now %d handlers)",
|
||||
NS_ConvertUTF16toUTF8(name).get(), handler.get(),
|
||||
mActiveHandlers.Count()));
|
||||
|
||||
nsresult rv = handler->InitAlertAsync(aAlert);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Failed to init alert, removing '%s'",
|
||||
NS_ConvertUTF16toUTF8(name).get()));
|
||||
mActiveHandlers.Remove(name);
|
||||
handler->UnregisterHandler();
|
||||
return rv;
|
||||
|
@ -448,6 +456,7 @@ ToastNotification::ShowAlert(nsIAlertNotification* aAlert,
|
|||
|
||||
NS_IMETHODIMP
|
||||
ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification* aAlert,
|
||||
const nsAString& aWindowsTag,
|
||||
nsAString& aString) {
|
||||
NS_ENSURE_ARG(aAlert);
|
||||
|
||||
|
@ -488,12 +497,84 @@ ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification* aAlert,
|
|||
text, hostPort, textClickable, requireInteraction, actions,
|
||||
isSystemPrincipal, launchUrl);
|
||||
|
||||
// Usually, this will be empty during testing, making test output
|
||||
// deterministic.
|
||||
MOZ_TRY(handler->SetWindowsTag(aWindowsTag));
|
||||
|
||||
nsAutoString imageURL;
|
||||
MOZ_TRY(aAlert->GetImageURL(imageURL));
|
||||
|
||||
return handler->CreateToastXmlString(imageURL, aString);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ToastNotification::HandleWindowsTag(const nsAString& aWindowsTag,
|
||||
nsIUnknownWindowsTagListener* aListener,
|
||||
bool* aRetVal) {
|
||||
*aRetVal = false;
|
||||
NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED);
|
||||
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Iterating %d handlers", mActiveHandlers.Count()));
|
||||
|
||||
for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) {
|
||||
RefPtr<ToastNotificationHandler> handler = iter.UserData();
|
||||
nsAutoString tag;
|
||||
nsresult rv = handler->GetWindowsTag(tag);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Comparing external windowsTag '%s' to handled windowsTag '%s'",
|
||||
NS_ConvertUTF16toUTF8(aWindowsTag).get(),
|
||||
NS_ConvertUTF16toUTF8(tag).get()));
|
||||
if (aWindowsTag.Equals(tag)) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("External windowsTag '%s' is handled by handler [%p]",
|
||||
NS_ConvertUTF16toUTF8(aWindowsTag).get(), handler.get()));
|
||||
*aRetVal = true;
|
||||
|
||||
nsString eventName(aWindowsTag);
|
||||
nsAutoHandle event(
|
||||
OpenEventW(EVENT_MODIFY_STATE, FALSE, eventName.get()));
|
||||
if (event.get()) {
|
||||
if (SetEvent(event)) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Info,
|
||||
("Set event for event named '%s'",
|
||||
NS_ConvertUTF16toUTF8(eventName).get()));
|
||||
} else {
|
||||
MOZ_LOG(
|
||||
sWASLog, LogLevel::Error,
|
||||
("Failed to set event for event named '%s' (GetLastError=%lu)",
|
||||
NS_ConvertUTF16toUTF8(eventName).get(), GetLastError()));
|
||||
}
|
||||
} else {
|
||||
MOZ_LOG(sWASLog, LogLevel::Error,
|
||||
("Failed to open event named '%s' (GetLastError=%lu)",
|
||||
NS_ConvertUTF16toUTF8(eventName).get(), GetLastError()));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
} else {
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Failed to get windowsTag for handler [%p]", handler.get()));
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug, ("aListener [%p]", aListener));
|
||||
if (aListener) {
|
||||
nsAutoString launchUrl;
|
||||
MOZ_TRY(ToastNotificationHandler::FindLaunchURLForWindowsTag(
|
||||
aWindowsTag, mAumid.ref(), launchUrl));
|
||||
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Found launchUrl [%s]", NS_ConvertUTF16toUTF8(launchUrl).get()));
|
||||
aListener->HandleUnknownWindowsTag(aWindowsTag, launchUrl);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ToastNotification::CloseAlert(const nsAString& aAlertName) {
|
||||
RefPtr<ToastNotificationHandler> handler;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#endif
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Tokenizer.h"
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
|
@ -41,6 +42,8 @@
|
|||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
||||
extern LazyLogModule sWASLog;
|
||||
|
||||
using namespace ABI::Windows::Data::Xml::Dom;
|
||||
using namespace ABI::Windows::Foundation;
|
||||
using namespace ABI::Windows::UI::Notifications;
|
||||
|
@ -146,6 +149,16 @@ static bool AddActionNode(ComPtr<IXmlDocument>& toastXml,
|
|||
return true;
|
||||
}
|
||||
|
||||
nsresult ToastNotificationHandler::GetWindowsTag(nsAString& aWindowsTag) {
|
||||
aWindowsTag.Assign(mWindowsTag);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult ToastNotificationHandler::SetWindowsTag(const nsAString& aWindowsTag) {
|
||||
mWindowsTag.Assign(aWindowsTag);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// clang - format off
|
||||
/* Populate the launch argument so the COM server can reconstruct the toast
|
||||
* origin.
|
||||
|
@ -217,6 +230,9 @@ Result<nsString, nsresult> ToastNotificationHandler::GetLaunchArgument() {
|
|||
launchArg += u"\nlaunchUrl\n"_ns + mLaunchUrl;
|
||||
}
|
||||
|
||||
// `windowsTag` argument.
|
||||
launchArg += u"\nwindowsTag\n"_ns + mWindowsTag;
|
||||
|
||||
return launchArg;
|
||||
}
|
||||
|
||||
|
@ -414,6 +430,9 @@ ComPtr<IXmlDocument> ToastNotificationHandler::CreateToastXmlDocument() {
|
|||
success = SetAttribute(toastElement, HStringReference(L"launch"), launchArg);
|
||||
NS_ENSURE_TRUE(success, nullptr);
|
||||
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("launchArg: '%s'", NS_ConvertUTF16toUTF8(launchArg).get()));
|
||||
|
||||
ComPtr<IXmlElement> actions;
|
||||
hr = toastXml->CreateElement(HStringReference(L"actions").Get(), &actions);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
@ -644,6 +663,8 @@ HRESULT
|
|||
ToastNotificationHandler::OnActivate(
|
||||
const ComPtr<IToastNotification>& notification,
|
||||
const ComPtr<IInspectable>& inspectable) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Info, ("OnActivate"));
|
||||
|
||||
if (mAlertListener) {
|
||||
// Extract the `action` value from the argument string.
|
||||
nsAutoString actionString;
|
||||
|
@ -657,6 +678,10 @@ ToastNotificationHandler::OnActivate(
|
|||
uint32_t len = 0;
|
||||
const char16_t* buffer = (char16_t*)arguments.GetRawBuffer(&len);
|
||||
if (buffer) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Info,
|
||||
("OnActivate: arguments: %s",
|
||||
NS_ConvertUTF16toUTF8(buffer).get()));
|
||||
|
||||
// Toast arguments are a newline separated key/value combination of
|
||||
// launch arguments and an optional action argument provided as an
|
||||
// argument to the toast's constructor. After the `action` key is
|
||||
|
@ -711,51 +736,43 @@ ToastNotificationHandler::OnActivate(
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// A single toast message can receive multiple dismiss events, at most one for
|
||||
// the popup and at most one for the action center. We can't simply count
|
||||
// dismiss events as the user may have disabled either popups or action center
|
||||
// notifications, therefore we have to check if the toast remains in the history
|
||||
// (action center) to determine if the toast is fully dismissed.
|
||||
static bool NotificationStillPresent(
|
||||
const ComPtr<IToastNotification>& current_notification, nsString& nsAumid) {
|
||||
// Returns `nullptr` if no such toast exists.
|
||||
/* static */ ComPtr<IToastNotification>
|
||||
ToastNotificationHandler::FindNotificationByTag(const nsAString& aWindowsTag,
|
||||
const nsAString& nsAumid) {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
ComPtr<IToastNotification2> current_notification2;
|
||||
hr = current_notification.As(¤t_notification2);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
|
||||
HString current_id;
|
||||
hr = current_notification2->get_Tag(current_id.GetAddressOf());
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
current_id.Set(PromiseFlatString(aWindowsTag).get());
|
||||
|
||||
ComPtr<IToastNotificationManagerStatics> manager =
|
||||
GetToastNotificationManagerStatics();
|
||||
if (!manager) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ComPtr<IToastNotificationManagerStatics2> manager2;
|
||||
hr = manager.As(&manager2);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
||||
ComPtr<IToastNotificationHistory> history;
|
||||
hr = manager2->get_History(&history);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
ComPtr<IToastNotificationHistory2> history2;
|
||||
hr = history.As(&history2);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
||||
HString aumid;
|
||||
hr = aumid.Set(nsAumid.get());
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
hr = aumid.Set(PromiseFlatString(nsAumid).get());
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
||||
ComPtr<IVectorView_ToastNotification> toasts;
|
||||
hr = history2->GetHistoryWithId(aumid.Get(), &toasts);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
||||
unsigned int hist_size;
|
||||
hr = toasts->get_Size(&hist_size);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
for (unsigned int i = 0; i < hist_size; i++) {
|
||||
ComPtr<IToastNotification> hist_toast;
|
||||
hr = toasts->GetAt(i, &hist_toast);
|
||||
|
@ -765,30 +782,120 @@ static bool NotificationStillPresent(
|
|||
|
||||
ComPtr<IToastNotification2> hist_toast2;
|
||||
hr = hist_toast.As(&hist_toast2);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
||||
HString history_id;
|
||||
hr = hist_toast2->get_Tag(history_id.GetAddressOf());
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
|
||||
|
||||
// We can not directly compare IToastNotification objects; their IUnknown
|
||||
// pointers should be equivalent but under inspection were not. Therefore we
|
||||
// use the notification's tag instead.
|
||||
if (current_id == history_id) {
|
||||
return true;
|
||||
return hist_toast;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* static */ HRESULT ToastNotificationHandler::GetLaunchArgumentValueForKey(
|
||||
const ComPtr<IToastNotification> toast, const nsAString& key,
|
||||
nsAString& value) {
|
||||
ComPtr<IXmlDocument> xml;
|
||||
HRESULT hr = toast->get_Content(&xml);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
|
||||
ComPtr<IXmlElement> root;
|
||||
hr = xml->get_DocumentElement(&root);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
|
||||
HString launchHString;
|
||||
hr = root->GetAttribute(HStringReference(L"launch").Get(),
|
||||
launchHString.GetAddressOf());
|
||||
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
|
||||
unsigned int len;
|
||||
const wchar_t* launchPtr = launchHString.GetRawBuffer(&len);
|
||||
nsAutoString launch(launchPtr, len);
|
||||
|
||||
// Toast arguments are a newline separated key/value combination of launch
|
||||
// arguments and an optional action argument provided as an argument to the
|
||||
// toast's constructor. After the `action` key is found, the remainder of
|
||||
// toast argument (including newlines) is the `action` value.
|
||||
Tokenizer16 parse((char16_t*)launchPtr);
|
||||
nsDependentSubstring token;
|
||||
|
||||
while (parse.ReadUntil(Tokenizer16::Token::NewLine(), token)) {
|
||||
if (token == u"action"_ns) {
|
||||
// As soon as we see an action, we're done: we don't want to take a "key"
|
||||
// from the user-provided action string.
|
||||
return E_FAIL;
|
||||
} else if (token.Equals(key)) {
|
||||
Unused << parse.ReadUntil(Tokenizer16::Token::NewLine(), value);
|
||||
return S_OK;
|
||||
} else {
|
||||
// Next line is a value in a key/value pair, skip.
|
||||
parse.SkipUntil(Tokenizer16::Token::NewLine());
|
||||
// Skip newline.
|
||||
Tokenizer16::Token unused;
|
||||
Unused << parse.Next(unused);
|
||||
}
|
||||
}
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
/* static */ nsresult ToastNotificationHandler::FindLaunchURLForWindowsTag(
|
||||
const nsAString& aWindowsTag, const nsAString& aAumid,
|
||||
nsAString& aLaunchUrl) {
|
||||
aLaunchUrl.Truncate();
|
||||
|
||||
ComPtr<IToastNotification> toast =
|
||||
ToastNotificationHandler::FindNotificationByTag(aWindowsTag, aAumid);
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug, ("Found toast [%p]", toast.Get()));
|
||||
|
||||
if (!toast) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
HRESULT hr = ToastNotificationHandler::GetLaunchArgumentValueForKey(
|
||||
toast, u"launchUrl"_ns, aLaunchUrl);
|
||||
|
||||
if (!SUCCEEDED(hr)) {
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Did not find launchUrl [hr=0x%08lX]", hr));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_LOG(sWASLog, LogLevel::Debug,
|
||||
("Found launchUrl [%s]", NS_ConvertUTF16toUTF8(aLaunchUrl).get()));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// A single toast message can receive multiple dismiss events, at most one for
|
||||
// the popup and at most one for the action center. We can't simply count
|
||||
// dismiss events as the user may have disabled either popups or action center
|
||||
// notifications, therefore we have to check if the toast remains in the history
|
||||
// (action center) to determine if the toast is fully dismissed.
|
||||
HRESULT
|
||||
ToastNotificationHandler::OnDismiss(
|
||||
const ComPtr<IToastNotification>& notification,
|
||||
const ComPtr<IToastDismissedEventArgs>& aArgs) {
|
||||
// Don't dismiss notifications when they are still in the action center. We
|
||||
// can receive multiple dismiss events.
|
||||
if (NotificationStillPresent(notification, mAumid)) {
|
||||
ComPtr<IToastNotification2> notification2;
|
||||
HRESULT hr = notification.As(¬ification2);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
|
||||
|
||||
HString tagHString;
|
||||
hr = notification2->get_Tag(tagHString.GetAddressOf());
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
|
||||
|
||||
unsigned int len;
|
||||
const wchar_t* tagPtr = tagHString.GetRawBuffer(&len);
|
||||
nsAutoString tag(tagPtr, len);
|
||||
|
||||
if (FindNotificationByTag(tag, mAumid)) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,14 @@ class ToastNotificationHandler final
|
|||
|
||||
nsresult CreateToastXmlString(const nsAString& aImageURL, nsAString& aString);
|
||||
|
||||
nsresult GetWindowsTag(nsAString& aWindowsTag);
|
||||
nsresult SetWindowsTag(const nsAString& aWindowsTag);
|
||||
|
||||
// Exposed for consumption by `ToastNotification.cpp`.
|
||||
static nsresult FindLaunchURLForWindowsTag(const nsAString& aWindowsTag,
|
||||
const nsAString& aAumid,
|
||||
nsAString& aLaunchUrl);
|
||||
|
||||
protected:
|
||||
virtual ~ToastNotificationHandler();
|
||||
|
||||
|
@ -121,6 +129,12 @@ class ToastNotificationHandler final
|
|||
const ComPtr<IToastDismissedEventArgs>& aArgs);
|
||||
HRESULT OnFail(const ComPtr<IToastNotification>& notification,
|
||||
const ComPtr<IToastFailedEventArgs>& aArgs);
|
||||
|
||||
static HRESULT GetLaunchArgumentValueForKey(
|
||||
const ComPtr<IToastNotification> toast, const nsAString& key,
|
||||
nsAString& value);
|
||||
static ComPtr<IToastNotification> FindNotificationByTag(
|
||||
const nsAString& aWindowsTag, const nsAString& nsAumid);
|
||||
};
|
||||
|
||||
} // namespace widget
|
||||
|
|
|
@ -71,6 +71,9 @@ function testAlert(when, { serverEnabled, profD, isBackgroundTaskMode } = {}) {
|
|||
if (serverEnabled && launchUrl) {
|
||||
s += `
launchUrl
${launchUrl}`;
|
||||
}
|
||||
if (serverEnabled) {
|
||||
s += "
windowsTag
";
|
||||
}
|
||||
if (argument) {
|
||||
s += `
action
${argument}`;
|
||||
}
|
||||
|
@ -205,7 +208,10 @@ function testAlert(when, { serverEnabled, profD, isBackgroundTaskMode } = {}) {
|
|||
"settings",
|
||||
launchUrl
|
||||
)}" placement="contextmenu"/>`;
|
||||
expected = `<toast launch="${argumentString(null, launchUrl)}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsActionWithLaunchUrl}</actions></toast>`;
|
||||
expected = `<toast launch="${argumentString(
|
||||
null,
|
||||
launchUrl
|
||||
)}"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsActionWithLaunchUrl}</actions></toast>`;
|
||||
Assert.equal(
|
||||
expected.replace("<actions></actions>", "<actions/>"),
|
||||
alertsService.getXmlStringForWindowsAlert(alert),
|
||||
|
|
Загрузка…
Ссылка в новой задаче