/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sts=2 sw=2 et cin: */ /* 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/. */
usingnamespace ABI::Windows::Foundation; usingnamespace Microsoft::WRL; usingnamespace Microsoft::WRL::Wrappers; // Needed to disambiguate internal and Windows `ToastNotification` classes. usingnamespace ABI::Windows::UI::Notifications; using WinToastNotification = ABI::Windows::UI::Notifications::ToastNotification; using IVectorView_ToastNotification =
ABI::Windows::Foundation::Collections::IVectorView<WinToastNotification*>; using IVectorView_ScheduledToastNotification =
ABI::Windows::Foundation::Collections::IVectorView<
ScheduledToastNotification*>;
nsresult ToastNotification::Init() { if (!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { // Windows Toast Notification requires AppId. But allow `xpcshell` to // create the service to test other functionality. if (!EnsureAumidRegistered()) {
MOZ_LOG(sWASLog, LogLevel::Warning, ("Failed to register AUMID!")); return NS_ERROR_NOT_IMPLEMENTED;
}
} else {
MOZ_LOG(sWASLog, LogLevel::Info, ("Using dummy AUMID in xpcshell test"));
mAumid.emplace(u"XpcshellTestToastAumid"_ns);
}
bool ToastNotification::EnsureAumidRegistered() { // Check if this is an MSIX install, app identity is provided by the package // so no registration is necessary. if (AssignIfMsixAumid(mAumid)) {
MOZ_LOG(
sWASLog, LogLevel::Info,
("Found MSIX AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid.ref()).get())); returntrue;
}
// Check if toasts were registered during NSIS/MSI installation. if (AssignIfNsisAumid(installHash, mAumid)) {
MOZ_LOG(sWASLog, LogLevel::Info,
("Found AUMID from installer (with install hash '%s'): '%s'",
NS_ConvertUTF16toUTF8(installHash).get(),
NS_ConvertUTF16toUTF8(mAumid.ref()).get())); returntrue;
}
// No AUMID registered, fall through to runtime registration for development // and portable builds. if (RegisterRuntimeAumid(installHash, mAumid)) {
MOZ_LOG(
sWASLog, LogLevel::Info,
("Updated AUMID registration at runtime (for install hash '%s'): '%s'",
NS_ConvertUTF16toUTF8(installHash).get(),
NS_ConvertUTF16toUTF8(mAumid.ref()).get())); returntrue;
}
MOZ_LOG(sWASLog, LogLevel::Warning,
("Failed to register AUMID at runtime! (for install hash '%s')",
NS_ConvertUTF16toUTF8(installHash).get())); returnfalse;
}
bool ToastNotification::AssignIfMsixAumid(Maybe<nsAutoString>& aAumid) {
UINT32 len = 0; // ERROR_INSUFFICIENT_BUFFER signals that we're in an MSIX package, and // therefore should use the package's AUMID. if (GetCurrentApplicationUserModelId(&len, nullptr) !=
ERROR_INSUFFICIENT_BUFFER) {
MOZ_LOG(sWASLog, LogLevel::Debug, ("Not an MSIX package")); returnfalse;
}
mozilla::Buffer<wchar_t> buffer(len); LONG success = GetCurrentApplicationUserModelId(&len, buffer.Elements());
NS_ENSURE_TRUE(success == ERROR_SUCCESS, false);
aAumid.emplace(buffer.Elements()); returntrue;
}
bool ToastNotification::AssignIfNsisAumid(nsAutoString& aInstallHash,
Maybe<nsAutoString>& aAumid) {
nsAutoString nsisAumidName =
u""_ns MOZ_TOAST_APP_NAME u"Toast-"_ns + aInstallHash;
nsAutoString nsisAumidPath = u"AppUserModelId\\"_ns + nsisAumidName; if (!WinRegistry::HasKey(HKEY_CLASSES_ROOT, nsisAumidPath)) {
MOZ_LOG(sWASLog, LogLevel::Debug,
("No CustomActivator value from installer in key 'HKCR\\%s'",
NS_ConvertUTF16toUTF8(nsisAumidPath).get())); returnfalse;
}
aAumid.emplace(nsisAumidName); returntrue;
}
bool ToastNotification::RegisterRuntimeAumid(nsAutoString& aInstallHash,
Maybe<nsAutoString>& aAumid) { // Portable AUMID slightly differs from installed AUMID so we can // differentiate installed to HKCU vs portable installs if necessary.
nsAutoString portableAumid =
u""_ns MOZ_TOAST_APP_NAME u"PortableToast-"_ns + aInstallHash;
nsAutoString title;
MOZ_TRY(aAlert->GetTitle(title)); if (!EnsureUTF16Validity(title)) {
MOZ_LOG(sWASLog, LogLevel::Warning,
("Notification title was invalid UTF16, unpaired surrogates have " "been replaced."));
}
nsAutoString text;
MOZ_TRY(aAlert->GetText(text)); if (!EnsureUTF16Validity(text)) {
MOZ_LOG(sWASLog, LogLevel::Warning,
("Notification text was invalid UTF16, unpaired surrogates have " "been replaced."));
}
// Verifies that the tag recieved associates to a notification created during // this application's session, or handles fallback behavior.
RefPtr<ToastHandledPromise> ToastNotification::VerifyTagPresentOrFallback( const nsAString& aWindowsTag) {
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())); return ToastHandledPromise::CreateAndResolve(true, __func__);
}
} else {
MOZ_LOG(sWASLog, LogLevel::Debug,
("Failed to get windowsTag for handler [%p]", handler.get()));
}
}
// Fallback handling is required.
MOZ_LOG(sWASLog, LogLevel::Debug,
("External windowsTag '%s' is not handled",
NS_ConvertUTF16toUTF8(aWindowsTag).get()));
RefPtr<ToastHandledPromise::Private> fallbackPromise = new ToastHandledPromise::Private(__func__);
// TODO: Bug 1806005 - At time of writing this function is called in a call // stack containing `WndProc` callback on an STA thread. As a result attempts // to create a `ToastNotificationManager` instance results an an // `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` error. We can simplify the the XPCOM // interface and synchronize the COM interactions if notification fallback // handling were no longer handled in a `WndProc` context.
NS_DispatchBackgroundTask(NS_NewRunnableFunction( "VerifyTagPresentOrFallback fallback background task",
[fallbackPromise]() { fallbackPromise->Resolve(false, __func__); }));
return fallbackPromise;
}
// Send our window's PID to the notification server so that it can grant us // `SetForegroundWindow` permissions. PID 0 is sent to signal no window PID. // Absense of PID which may occur when we are yet unable to retrieve the // window during startup, which is not a problem in practice as new windows // receive focus by default. void ToastNotification::SignalComNotificationHandled( const nsAString& aWindowsTag) {
DWORD pid = 0;
nsCOMPtr<nsIWindowMediator> winMediator(
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); if (winMediator) {
nsCOMPtr<mozIDOMWindowProxy> navWin;
winMediator->GetMostRecentBrowserWindow(getter_AddRefs(navWin)); if (navWin) {
nsCOMPtr<nsIWidget> widget =
WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin)); if (widget) {
HWND hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
GetWindowThreadProcessId(hwnd, &pid);
} else {
MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get widget"));
}
} else {
MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get navWin"));
}
} else {
MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get WinMediator"));
}
// Run pipe communication off the main thread to prevent UI jank from // blocking. Nothing relies on the COM server's response or that it has // responded at time of commit.
NS_DispatchBackgroundTask(
NS_NewRunnableFunction( "SignalComNotificationHandled background task",
[pid, aWindowsTag = nsString{aWindowsTag}]() mutable {
std::wstring pipeName = GetNotificationPipeName(aWindowsTag.get());
nsAutoHandle pipe;
pipe.own(CreateFileW(pipeName.c_str(), GENERIC_READ | GENERIC_WRITE,
0, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr)); if (pipe.get() == INVALID_HANDLE_VALUE) {
MOZ_LOG(sWASLog, LogLevel::Error,
("Unable to open notification server pipe.")); return;
}
// Pass our window's PID to the COM server receive // `SetForegroundWindow` permissions, and wait for a message // acknowledging the permission has been granted.
ToastNotificationPidMessage in{};
in.pid = pid;
ToastNotificationPermissionMessage out{}; auto transact = [&](OVERLAPPED& overlapped) { return TransactNamedPipe(pipe.get(), &in, sizeof(in), &out, sizeof(out), nullptr, &overlapped);
}; bool result =
SyncDoOverlappedIOWithTimeout(pipe, sizeof(out), transact);
if (result && out.setForegroundPermissionGranted && pid != 0) {
MOZ_LOG(
sWASLog, LogLevel::Info,
("SetForegroundWindow permission granted to our window."));
} else {
MOZ_LOG(sWASLog, LogLevel::Error,
("SetForegroundWindow permission not granted to our " "window."));
}
}),
NS_DISPATCH_EVENT_MAY_BLOCK);
}
this->VerifyTagPresentOrFallback(aWindowsTag)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[aWindowsTag = nsString(aWindowsTag),
promise](constbool aTagWasHandled) { // We no longer need to query toast information from OS and can // allow the COM server to proceed (toast information is lost once // the COM server's `Activate` callback returns).
SignalComNotificationHandled(aWindowsTag);
dom::AutoJSAPI js; if (NS_WARN_IF(!js.Init(promise->GetGlobalObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE); return;
}
// Resolve the DOM Promise with a JS object. Set properties if // fallback handling is necessary.
promise->MaybeResolve(obj);
},
[aWindowsTag = nsString(aWindowsTag), promise]() { // We no longer need to query toast information from OS and can // allow the COM server to proceed (toast information is lost once // the COM server's `Activate` callback returns).
SignalComNotificationHandled(aWindowsTag);
promise->MaybeReject(NS_ERROR_FAILURE);
});
promise.forget(aPromise); return NS_OK;
}
NS_IMETHODIMP
ToastNotification::CloseAlert(const nsAString& aAlertName, bool aContextClosed) {
RefPtr<ToastNotificationHandler> handler; if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { // This can happen when the handler is gone but the closure signal is not // yet reached to the content process, and then the process tries closing // the same signal. return NS_OK;
}
if (!aContextClosed || handler->IsPrivate()) { // Hide the alert when not implicitly closed by tab/window closing or when // notification originated from a private tab.
handler->HideAlert();
}
void ToastNotification::RemoveHandler(const nsAString& aAlertName,
ToastNotificationHandler* aHandler) { // The alert may have been replaced; only remove it from the active // handler's map if it's the same. if (IsActiveHandler(aAlertName, aHandler)) { // Terrible things happen if the destructor of a handler is called inside // the hashtable .Remove() method. Wait until we have returned from there.
RefPtr<ToastNotificationHandler> kungFuDeathGrip(aHandler);
mActiveHandlers.Remove(aAlertName);
aHandler->UnregisterHandler();
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.