File: notification_activator.cc

package info (click to toggle)
chromium 139.0.7258.127-2
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 6,122,156 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (196 lines) | stat: -rw-r--r-- 7,263 bytes parent folder | download | duplicates (6)
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