1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/notification_helper/notification_activator.h"
#include <windows.h>
#include <shellapi.h>
#include <string>
#include "base/command_line.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/process.h"
#include "base/win/windows_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/notification_helper/notification_helper_util.h"
#include "chrome/notification_helper/trace_util.h"
namespace {
// The response entered by the user while interacting with the toast.
const wchar_t kUserResponse[] = L"userResponse";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class NotificationActivatorPrimaryStatus {
kSuccess = 0,
kChromeExeMissing = 1,
kShellExecuteFailed = 2,
kMaxValue = kShellExecuteFailed,
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class NotificationActivatorSecondaryStatus {
kSuccess = 0,
kLaunchIdEmpty = 1 << 0,
kAllowSetForegroundWindowFailed = 1 << 1,
kProcessHandleMissing = 1 << 2,
kScenarioCount = 1 << 3,
kMaxValue = kScenarioCount,
};
void LogNotificationActivatorPrimaryStatus(
NotificationActivatorPrimaryStatus status) {
UMA_HISTOGRAM_ENUMERATION(
"Notifications.NotificationHelper.NotificationActivatorPrimaryStatus",
status);
}
void LogNotificationActivatorSecondaryStatus(
NotificationActivatorSecondaryStatus status) {
UMA_HISTOGRAM_ENUMERATION(
"Notifications.NotificationHelper.NotificationActivatorSecondaryStatus",
status);
}
} // namespace
namespace notification_helper {
NotificationActivator::~NotificationActivator() = default;
// Handles toast activation outside of the browser process lifecycle by
// launching chrome.exe with --notification-launch-id. This new process may
// rendezvous to an existing browser process or become a new one, as
// appropriate.
//
// When this method is called, there are three possibilities depending on the
// running state of Chrome.
// 1) NOT_RUNNING: Chrome is not running.
// 2) NEW_INSTANCE: Chrome is running, but it's NOT the same instance that sent
// the toast.
// 3) SAME_INSTANCE : Chrome is running, and it _is_ the same instance that sent
// the toast.
//
// Chrome could attach an activation event handler to the toast so that Windows
// can call it directly to handle the activation. However, Windows makes this
// function call only in case SAME_INSTANCE. For the other two cases, Chrome
// needs to handle the activation on its own. Since there is no way to
// differentiate cases SAME_INSTANCE and NEW_INSTANCE in this
// notification_helper process, Chrome doesn't attach an activation event
// handler to the toast and handles all three cases through the command line.
HRESULT NotificationActivator::Activate(
LPCWSTR app_user_model_id,
LPCWSTR invoked_args,
const NOTIFICATION_USER_INPUT_DATA* data,
ULONG count) {
base::FilePath chrome_exe_path = GetChromeExePath();
if (chrome_exe_path.empty()) {
Trace(L"Failed to get chrome exe path\n");
LogNotificationActivatorPrimaryStatus(
NotificationActivatorPrimaryStatus::kChromeExeMissing);
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
int secondary_status =
static_cast<int>(NotificationActivatorSecondaryStatus::kSuccess);
// |invoked_args| contains the launch ID string encoded by Chrome. Chrome adds
// it to the launch argument of the toast and gets it back via |invoked_args|.
// Chrome needs the data to be able to look up the notification on its end.
//
// When the user clicks the Chrome app title rather than the notifications in
// the Action Center, an empty launch id string is generated. It is preferable
// to launch Chrome with this empty launch id in this scenario, which results
// in displaying a NTP.
if (invoked_args == nullptr || invoked_args[0] == 0) {
secondary_status |=
static_cast<int>(NotificationActivatorSecondaryStatus::kLaunchIdEmpty);
}
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitchNative(switches::kNotificationLaunchId,
invoked_args);
// Check to see if a user response (inline reply) is also supplied.
for (ULONG i = 0; i < count; ++i) {
if (lstrcmpW(kUserResponse, data[i].Key) == 0) {
command_line.AppendSwitchNative(switches::kNotificationInlineReply,
data[i].Value);
break;
}
}
std::wstring params(command_line.GetCommandLineString());
SHELLEXECUTEINFO info;
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(info);
info.fMask =
SEE_MASK_NOASYNC | SEE_MASK_FLAG_LOG_USAGE | SEE_MASK_NOCLOSEPROCESS;
info.lpFile = chrome_exe_path.value().c_str();
info.lpParameters = params.c_str();
info.nShow = SW_SHOWNORMAL;
if (!::ShellExecuteEx(&info)) {
DWORD error_code = ::GetLastError();
Trace(L"Unable to launch Chrome.exe; error: 0x%08X\n", error_code);
LogNotificationActivatorPrimaryStatus(
NotificationActivatorPrimaryStatus::kShellExecuteFailed);
return HRESULT_FROM_WIN32(error_code);
}
if (info.hProcess != nullptr) {
base::Process process(info.hProcess);
DWORD pid = ::GetProcessId(process.Handle());
// Despite the fact that the Windows notification center grants the helper
// permission to set the foreground window, the helper fails to pass the
// baton to Chrome at an alarming rate; see https://crbug.com/837796.
// Sending generic down/up key events seems to fix it.
INPUT keyboard_inputs[2] = {};
keyboard_inputs[0].type = INPUT_KEYBOARD;
keyboard_inputs[0].ki.dwFlags = 0; // Key press.
keyboard_inputs[1] = keyboard_inputs[0];
keyboard_inputs[1].ki.dwFlags |= KEYEVENTF_KEYUP; // key release.
::SendInput(2, keyboard_inputs, sizeof(keyboard_inputs[0]));
if (!::AllowSetForegroundWindow(pid)) {
#if !defined(NDEBUG)
DWORD error_code = ::GetLastError();
Trace(L"Unable to forward activation privilege; error: 0x%08X\n",
error_code);
#endif
// The lack of ability to set the window to foreground is not reason
// enough to fail the activation call. The user will see the Chrome icon
// flash in the task bar if this happens, which is a graceful failure.
secondary_status |=
static_cast<int>(NotificationActivatorSecondaryStatus::
kAllowSetForegroundWindowFailed);
}
} else {
secondary_status |= static_cast<int>(
NotificationActivatorSecondaryStatus::kProcessHandleMissing);
}
LogNotificationActivatorPrimaryStatus(
NotificationActivatorPrimaryStatus::kSuccess);
LogNotificationActivatorSecondaryStatus(
static_cast<NotificationActivatorSecondaryStatus>(secondary_status));
return S_OK;
}
} // namespace notification_helper
|