| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | 2 // Use of this source code is governed by a BSD-style license that can be | 
|  | 3 // found in the LICENSE file. | 
|  | 4 | 
|  | 5 #include "chrome/browser/notifications/notification_platform_bridge_win.h" | 
|  | 6 | 
|  | 7 #include <stdint.h> | 
|  | 8 #include <stdio.h> | 
|  | 9 | 
|  | 10 #include <algorithm> | 
|  | 11 #include <utility> | 
|  | 12 | 
|  | 13 #include "base/callback.h" | 
|  | 14 #include "base/files/file_util.h" | 
|  | 15 #include "base/logging.h" | 
|  | 16 #include "base/macros.h" | 
|  | 17 #include "base/memory/ref_counted_memory.h" | 
|  | 18 #include "base/strings/string16.h" | 
|  | 19 #include "base/strings/utf_string_conversions.h" | 
|  | 20 #include "base/task_runner.h" | 
|  | 21 #include "base/threading/sequenced_worker_pool.h" | 
|  | 22 #include "base/win/windows_version.h" | 
|  | 23 #include "chrome/browser/browser_process.h" | 
|  | 24 #include "chrome/browser/notifications/notification.h" | 
|  | 25 #include "chrome/browser/notifications/notification_toast_helper_win.h" | 
|  | 26 #include "chrome/browser/notifications/platform_notification_service_impl.h" | 
|  | 27 #include "content/public/browser/browser_thread.h" | 
|  | 28 #include "skia/ext/image_operations.h" | 
|  | 29 #include "third_party/skia/include/core/SkColor.h" | 
|  | 30 #include "ui/gfx/canvas.h" | 
|  | 31 #include "ui/gfx/geometry/rect_f.h" | 
|  | 32 #include "ui/gfx/geometry/size.h" | 
|  | 33 #include "ui/gfx/image/image.h" | 
|  | 34 #include "ui/gfx/image/image_skia.h" | 
|  | 35 #include "ui/message_center/notification_delegate.h" | 
|  | 36 | 
|  | 37 namespace { | 
|  | 38 | 
|  | 39 const int kNotificationIconSize = 32; | 
|  | 40 const SkColor kNotificationShadowColor = SkColorSetARGB(0.5 * 255, 0, 0, 0); | 
|  | 41 | 
|  | 42 wchar_t kToastXmlTemplate[] = | 
|  | 43     L"<toast>\n" | 
|  | 44     L"  <visual>\n" | 
|  | 45     L"    <binding template=\"ToastGeneric\">\n" | 
|  | 46     L"      <image placement=\"appLogoOverride\"/>\n" | 
|  | 47     L"      <text id=\"1\"></text>\n" | 
|  | 48     L"      <text id=\"2\"></text>\n" | 
|  | 49     L"    </binding>\n" | 
|  | 50     L"  </visual>\n" | 
|  | 51     L"  <actions>\n" | 
|  | 52     L"    <action activationType=\"background\" content=\"Settings...\" " | 
|  | 53     L"arguments=\"setting\"/>\n" | 
|  | 54     L"  </actions>\n" | 
|  | 55     L"</toast>"; | 
|  | 56 | 
|  | 57 // We use em space to pad <text> tag contents. Without this, then whenever | 
|  | 58 // <text> is left blank (or all-space), we'd see weird default text, e.g., | 
|  | 59 // "Chrome.(SOME STRING)" and "New notification". | 
|  | 60 wchar_t kEmSpace[] = L"\u2003"; | 
|  | 61 | 
|  | 62 wchar_t kAttrDataNotificationId[] = L"data-notification-id"; | 
|  | 63 wchar_t kAttrDataProfileId[] = L"data-profile-id"; | 
|  | 64 | 
|  | 65 // Shrinks |src_image| if necessary. Draws the resized image to the centre of a | 
|  | 66 // square image specified by |new_size| and |background_color|. Returns the | 
|  | 67 // result via |dst_image|. | 
|  | 68 void FormatImageToFitSquare(const gfx::Image& src_image, | 
|  | 69                             int new_size, | 
|  | 70                             SkColor background_color, | 
|  | 71                             gfx::Image* dst_image) { | 
|  | 72   int src_width = src_image.Width(); | 
|  | 73   int src_height = src_image.Height(); | 
|  | 74 | 
|  | 75   // Shrink image if it won't fit in square; otherwise just copy (don't expand). | 
|  | 76   gfx::ImageSkia resized_image; | 
|  | 77   if (src_width > new_size || src_height > new_size) { | 
|  | 78     int src_max_dim = std::max(src_width, src_height); | 
|  | 79     int resized_width = new_size * src_width / src_max_dim; | 
|  | 80     int resized_height = new_size * src_height / src_max_dim; | 
|  | 81     resized_image = gfx::ImageSkia::CreateFrom1xBitmap( | 
|  | 82         skia::ImageOperations::Resize( | 
|  | 83             *src_image.ToSkBitmap(), | 
|  | 84             skia::ImageOperations::RESIZE_LANCZOS3, | 
|  | 85             resized_width, | 
|  | 86             resized_height)); | 
|  | 87   } else { | 
|  | 88     resized_image = src_image.AsImageSkia(); | 
|  | 89   } | 
|  | 90 | 
|  | 91   // Create square image with |background_color|. | 
|  | 92   gfx::Canvas canvas(gfx::Size(new_size, new_size), 1.0f, false); | 
|  | 93   canvas.DrawRect(gfx::RectF(0, 0, new_size, new_size), background_color); | 
|  | 94 | 
|  | 95   // Draw the resized image at center of square image, and return. | 
|  | 96   canvas.DrawImageInt(resized_image, (new_size - resized_image.width()) / 2, | 
|  | 97                       (new_size - resized_image.height()) / 2); | 
|  | 98   *dst_image = gfx::Image::CreateFrom1xBitmap(canvas.GetBitmap()); | 
|  | 99 } | 
|  | 100 | 
|  | 101 }  // namespace | 
|  | 102 | 
|  | 103 // static | 
|  | 104 NotificationPlatformBridge* NotificationPlatformBridge::Create() { | 
|  | 105   return new NotificationPlatformBridgeWin(); | 
|  | 106 } | 
|  | 107 | 
|  | 108 // States of a toast notification. | 
|  | 109 struct NotificationToastSession : | 
|  | 110     public base::RefCounted<NotificationToastSession> { | 
|  | 111   NotificationToastSession(const std::string& notification_id_in, | 
|  | 112                            const std::string& profile_id_in, | 
|  | 113                            bool is_incognito_in, | 
|  | 114                            const Notification& notification_in) | 
|  | 115     : notification_id(notification_id_in), | 
|  | 116       profile_id(profile_id_in), | 
|  | 117       is_incognito(is_incognito_in), | 
|  | 118       notification(notification_in),  // Copy by value. | 
|  | 119       debug_direct_xml(false), | 
|  | 120       has_icon(!notification_in.icon().IsEmpty()) { | 
|  | 121     CHECK(base::win::GetVersion() >= base::win::VERSION_WIN10_R1); | 
|  | 122   } | 
|  | 123 | 
|  | 124   ~NotificationToastSession() { | 
|  | 125     // TODO(huangs): See if we can delete earlier, e.g., after brief delay?? | 
|  | 126     if (!temp_image_file.empty()) | 
|  | 127       DeleteTempImageFileOnFileThread(); | 
|  | 128   } | 
|  | 129 | 
|  | 130   // Deletes temporary icon on FILE thread: fire and forget. | 
|  | 131   void DeleteTempImageFileOnFileThread() { | 
|  | 132     content::BrowserThread::PostTask(content::BrowserThread::FILE, | 
|  | 133                                      FROM_HERE, | 
|  | 134         base::Bind(base::IgnoreResult(&base::DeleteFile), | 
|  | 135                    temp_image_file, | 
|  | 136                    false /* recursive */)); | 
|  | 137     temp_image_file.clear(); | 
|  | 138   } | 
|  | 139 | 
|  | 140   std::string notification_id; | 
|  | 141   std::string profile_id; | 
|  | 142   bool is_incognito; | 
|  | 143   const Notification notification; | 
|  | 144 | 
|  | 145   const bool has_icon; | 
|  | 146   gfx::Image formatted_icon; | 
|  | 147 | 
|  | 148   bool debug_direct_xml; | 
|  | 149 | 
|  | 150   base::FilePath temp_image_file; | 
|  | 151 | 
|  | 152   DISALLOW_COPY_AND_ASSIGN(NotificationToastSession); | 
|  | 153 }; | 
|  | 154 | 
|  | 155 // This callback is invoked when user clicks on the notification toast. This can | 
|  | 156 // occur at any time in Chrome's cycle, so we need to be robust. | 
|  | 157 HRESULT NotificationPlatformBridgeWin::ToastEventHandler::OnActivated( | 
|  | 158     winui::Notifications::IToastNotification* notification, | 
|  | 159     IInspectable* /* inspectable */) { | 
|  | 160   PostHandlerOnUIThread(EVENT_TYPE_ACTIVATED, notification); | 
|  | 161   return S_OK; | 
|  | 162 } | 
|  | 163 | 
|  | 164 HRESULT NotificationPlatformBridgeWin::ToastEventHandler::OnDismissed( | 
|  | 165     winui::Notifications::IToastNotification* notification, | 
|  | 166     winui::Notifications::IToastDismissedEventArgs* /* args */) { | 
|  | 167   PostHandlerOnUIThread(EVENT_TYPE_DISMISSED, notification); | 
|  | 168   return S_OK; | 
|  | 169 } | 
|  | 170 | 
|  | 171 HRESULT NotificationPlatformBridgeWin::ToastEventHandler::OnFailed( | 
|  | 172     winui::Notifications::IToastNotification* notification, | 
|  | 173     winui::Notifications::IToastFailedEventArgs* /* args */) { | 
|  | 174   PostHandlerOnUIThread(EVENT_TYPE_FAILED, notification); | 
|  | 175   return S_OK; | 
|  | 176 } | 
|  | 177 | 
|  | 178 // static | 
|  | 179 void NotificationPlatformBridgeWin::ToastEventHandler::PostHandlerOnUIThread( | 
|  | 180     EventType type, | 
|  | 181     winui::Notifications::IToastNotification* notification) { | 
|  | 182   // Extract the notifiation ID. | 
|  | 183   NotificationToastHelperWin helper; | 
|  | 184   helper.LoadNotificationAndXml(notification); | 
|  | 185   helper.SelectDocument(); | 
|  | 186   std::string notification_id = | 
|  | 187       base::UTF16ToUTF8(helper.GetAttribute(kAttrDataNotificationId)); | 
|  | 188   std::string profile_id = | 
|  | 189       base::UTF16ToUTF8(helper.GetAttribute(kAttrDataProfileId)); | 
|  | 190 | 
|  | 191   // Post task on UI thread. | 
|  | 192   content::BrowserThread::PostTask(content::BrowserThread::UI, | 
|  | 193                                    FROM_HERE, | 
|  | 194       base::Bind( | 
|  | 195           &NotificationPlatformBridgeWin::ToastEventHandler::HandleOnUIThread, | 
|  | 196           type, | 
|  | 197           notification_id, | 
|  | 198           profile_id)); | 
|  | 199 } | 
|  | 200 | 
|  | 201 // static | 
|  | 202 void NotificationPlatformBridgeWin::ToastEventHandler::HandleOnUIThread( | 
|  | 203     EventType type, | 
|  | 204     const std::string& notification_id, | 
|  | 205     const std::string& profile_id) { | 
|  | 206   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | 207 | 
|  | 208   // Get the NotificationPlatformBridgeWin singleton. | 
|  | 209   if (!g_browser_process) | 
|  | 210     return; | 
|  | 211   NotificationPlatformBridge* notification_bridge = | 
|  | 212       g_browser_process->notification_platform_bridge(); | 
|  | 213   if (!notification_bridge) | 
|  | 214     return; | 
|  | 215   NotificationPlatformBridgeWin* notification_bridge_win = | 
|  | 216       static_cast<NotificationPlatformBridgeWin*>(notification_bridge); | 
|  | 217 | 
|  | 218   switch (type) { | 
|  | 219     case EVENT_TYPE_ACTIVATED: { | 
|  | 220       notification_bridge_win->OnClickEvent(notification_id, profile_id); | 
|  | 221       notification_bridge_win->OnCloseEvent(notification_id, profile_id); | 
|  | 222       break; | 
|  | 223     } | 
|  | 224     case EVENT_TYPE_DISMISSED: { | 
|  | 225       notification_bridge_win->OnCloseEvent(notification_id, profile_id); | 
|  | 226       break; | 
|  | 227     } | 
|  | 228     case EVENT_TYPE_FAILED: { | 
|  | 229       break; | 
|  | 230     } | 
|  | 231   } | 
|  | 232 | 
|  | 233   // Currently we only perform cleanup. | 
|  | 234   notification_bridge_win->CleanupSession(notification_id); | 
|  | 235 } | 
|  | 236 | 
|  | 237 NotificationPlatformBridgeWin::ToastEventHandler | 
|  | 238     NotificationPlatformBridgeWin::toast_event_handler_; | 
|  | 239 | 
|  | 240 NotificationPlatformBridgeWin::~NotificationPlatformBridgeWin() {} | 
|  | 241 | 
|  | 242 void NotificationPlatformBridgeWin::Display( | 
|  | 243     NotificationCommon::Type notification_type, | 
|  | 244     const std::string& notification_id, | 
|  | 245     const std::string& profile_id, | 
|  | 246     bool is_incognito, | 
|  | 247     const Notification& notification) { | 
|  | 248   // TODO(huangs): Deal with |type|. | 
|  | 249   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | 250   if (session_map_.count(notification_id))  // Ignore duplicated session. | 
|  | 251     return; | 
|  | 252   scoped_refptr<NotificationToastSession> session = | 
|  | 253       new NotificationToastSession( | 
|  | 254           notification_id, profile_id, is_incognito, notification); | 
|  | 255   session_map_[notification_id] = session; | 
|  | 256 | 
|  | 257   // Debug code: Directly inject XML!! TODO(huangs): Remove. | 
|  | 258   if (session->notification.message().substr(0, 1) == L"<") { | 
|  | 259     session->debug_direct_xml = true; | 
|  | 260     DisplayStepMain(session); | 
|  | 261     return; | 
|  | 262   } | 
|  | 263 | 
|  | 264   if (!session->has_icon) { | 
|  | 265     // No icon: Can immediatly display. | 
|  | 266     DisplayStepMain(session); | 
|  | 267   } else { | 
|  | 268     // Has icon: Format image on a worker thread. | 
|  | 269     content::BrowserThread::GetBlockingPool() | 
|  | 270         ->GetTaskRunnerWithShutdownBehavior( | 
|  | 271             base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)->PostTask(FROM_HERE, | 
|  | 272                 base::Bind( | 
|  | 273         &NotificationPlatformBridgeWin::DisplayStepFormatIconOnWorkerThread, | 
|  | 274         base::Unretained(this), | 
|  | 275         session)); | 
|  | 276   } | 
|  | 277 } | 
|  | 278 | 
|  | 279 void NotificationPlatformBridgeWin::Close( | 
|  | 280     const std::string& profile_id, | 
|  | 281     const std::string& notification_id) { | 
|  | 282   // TODO(huangs): Implement. | 
|  | 283   ::MessageBox(NULL, L"Close()", L"Title", MB_OK); | 
|  | 284 } | 
|  | 285 | 
|  | 286 void NotificationPlatformBridgeWin::GetDisplayed( | 
|  | 287     const std::string& profile_id, | 
|  | 288     bool is_incognito, | 
|  | 289     const GetDisplayedNotificationsCallback& callback) const { | 
|  | 290   // TODO(huangs): Implement. | 
|  | 291 } | 
|  | 292 | 
|  | 293 void NotificationPlatformBridgeWin::SetReadyCallback( | 
|  | 294     NotificationBridgeReadyCallback callback) { | 
|  | 295   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | 296   std::move(callback).Run(true); | 
|  | 297 } | 
|  | 298 | 
|  | 299 void NotificationPlatformBridgeWin::DisplayStepFormatIconOnWorkerThread( | 
|  | 300     scoped_refptr<NotificationToastSession> session) { | 
|  | 301   FormatImageToFitSquare(session->notification.icon(), | 
|  | 302                          kNotificationIconSize, | 
|  | 303                          kNotificationShadowColor, | 
|  | 304                          &session->formatted_icon); | 
|  | 305 | 
|  | 306   // Save image to a temp file on the FILE thread. | 
|  | 307   content::BrowserThread::PostTask(content::BrowserThread::FILE, | 
|  | 308                                    FROM_HERE, | 
|  | 309       base::Bind( | 
|  | 310           &NotificationPlatformBridgeWin::DisplayStepPrepareIconOnFileThread, | 
|  | 311           base::Unretained(this), | 
|  | 312           session)); | 
|  | 313 } | 
|  | 314 | 
|  | 315 void NotificationPlatformBridgeWin::DisplayStepPrepareIconOnFileThread( | 
|  | 316     scoped_refptr<NotificationToastSession> session) { | 
|  | 317   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); | 
|  | 318 | 
|  | 319   // TODO(huangs): Possible alternative? | 
|  | 320   // We should set the image and launch params attribute in the notification | 
|  | 321   // XNL as described here: http://msdn.microsoft.com/en-us/library/hh465448 | 
|  | 322   // To set the image we may have to extract the image and specify it in the | 
|  | 323   // following url form. ms-appx:///images/foo.png | 
|  | 324   // The launch params as described don't get passed back to us via the | 
|  | 325   // winapp::Activation::ILaunchActivatedEventArgs argument. Needs to be | 
|  | 326   // investigated. | 
|  | 327 | 
|  | 328   // Write icon to temporary file store the filename. On failure, we clear the | 
|  | 329   // filename and allow flow to proceed without icon. | 
|  | 330   const gfx::Image& icon = session->formatted_icon.IsEmpty() ? | 
|  | 331       session->notification.icon() : session->formatted_icon; | 
|  | 332   scoped_refptr<base::RefCountedMemory> png = icon.As1xPNGBytes(); | 
|  | 333   base::FilePath temp_file; | 
|  | 334   session->temp_image_file.clear(); | 
|  | 335   // Create tempory file, and rename it to have ".png" extension because the API | 
|  | 336   // relies on extension. | 
|  | 337   if (base::CreateTemporaryFile(&temp_file)) { | 
|  | 338     base::FilePath temp_png_file = | 
|  | 339         temp_file.AddExtension(FILE_PATH_LITERAL(".png")); | 
|  | 340     if (base::ReplaceFile(temp_file, temp_png_file, nullptr)) { | 
|  | 341       if (base::WriteFile(temp_png_file, | 
|  | 342                           reinterpret_cast<const char *>(png->front()), | 
|  | 343                           png->size()) >= 0) { | 
|  | 344         session->temp_image_file = temp_png_file; | 
|  | 345       } else { | 
|  | 346         base::DeleteFile(temp_png_file, false); | 
|  | 347       } | 
|  | 348     } else { | 
|  | 349       base::DeleteFile(temp_file, false); | 
|  | 350     } | 
|  | 351   } | 
|  | 352 | 
|  | 353   content::BrowserThread::PostTask(content::BrowserThread::UI, | 
|  | 354                                    FROM_HERE, | 
|  | 355       base::Bind(&NotificationPlatformBridgeWin::DisplayStepMain, | 
|  | 356                  base::Unretained(this), | 
|  | 357                  session)); | 
|  | 358 } | 
|  | 359 | 
|  | 360 bool NotificationPlatformBridgeWin::DisplayStepMainWorker( | 
|  | 361     scoped_refptr<NotificationToastSession> session) { | 
|  | 362   NotificationToastHelperWin helper; | 
|  | 363   helper.CreateToastManager(); | 
|  | 364 | 
|  | 365   if (session->debug_direct_xml) { | 
|  | 366     // TODO(huangs): Remove debugging code. | 
|  | 367     helper.LoadXMLFromString(session->notification.message()); | 
|  | 368 | 
|  | 369   } else { | 
|  | 370     // Load template XML and customize it. | 
|  | 371     helper.LoadXMLFromString(kToastXmlTemplate); | 
|  | 372 | 
|  | 373     helper.SelectElementByTagNameAndIndex(L"image", 0); | 
|  | 374     if (session->temp_image_file.empty()) { | 
|  | 375       helper.RemoveElement(); | 
|  | 376 | 
|  | 377     } else { | 
|  | 378       base::string16 file_url = | 
|  | 379           helper.FilePathToFileUrl(session->temp_image_file); | 
|  | 380       helper.SetAttribute(L"src", file_url); | 
|  | 381     } | 
|  | 382 | 
|  | 383     helper.SelectElementByTagNameAndIndex(L"text", 0); | 
|  | 384     helper.AppendText(session->notification.title() + kEmSpace); | 
|  | 385 | 
|  | 386     helper.SelectElementByTagNameAndIndex(L"text", 1); | 
|  | 387     helper.AppendText(session->notification.message() + kEmSpace); | 
|  | 388   } | 
|  | 389 | 
|  | 390   helper.SelectDocument(); | 
|  | 391   helper.SetAttribute(L"duration", L"long"); | 
|  | 392   // Store notification ID as part of XML, retrieved later in event handlers. | 
|  | 393   helper.SetAttribute(kAttrDataNotificationId, | 
|  | 394                       base::UTF8ToUTF16(session->notification_id).c_str()); | 
|  | 395   helper.SetAttribute(kAttrDataProfileId, | 
|  | 396                       base::UTF8ToUTF16(session->profile_id).c_str()); | 
|  | 397 | 
|  | 398   helper.AlertToastXml();  // Debug code. | 
|  | 399   helper.CreateToastNotification(); | 
|  | 400   helper.CreateToastNotifier(); | 
|  | 401 | 
|  | 402   if (helper.HasFailed()) | 
|  | 403     return false; | 
|  | 404 | 
|  | 405   auto activated_handler = mswr::Callback<ToastActivatedHandler>( | 
|  | 406       &toast_event_handler_, | 
|  | 407       &NotificationPlatformBridgeWin::ToastEventHandler::OnActivated); | 
|  | 408   auto dismissed_handler = mswr::Callback<ToastDismissedHandler>( | 
|  | 409       &toast_event_handler_, | 
|  | 410       &NotificationPlatformBridgeWin::ToastEventHandler::OnDismissed); | 
|  | 411   auto failed_handler = mswr::Callback<ToastFailedHandler>( | 
|  | 412       &toast_event_handler_, | 
|  | 413       &NotificationPlatformBridgeWin::ToastEventHandler::OnFailed); | 
|  | 414 | 
|  | 415   // TODO(huangs): Pass these to |session|, and unsubscribe using these. | 
|  | 416   EventRegistrationToken activated_token; | 
|  | 417   EventRegistrationToken dismissed_token; | 
|  | 418   EventRegistrationToken failed_token; | 
|  | 419 | 
|  | 420   helper.Show(activated_handler, dismissed_handler, failed_handler, | 
|  | 421               &activated_token, &dismissed_token, &failed_token); | 
|  | 422   return helper.StillOkay(); | 
|  | 423 } | 
|  | 424 | 
|  | 425 void NotificationPlatformBridgeWin::DisplayStepMain( | 
|  | 426     scoped_refptr<NotificationToastSession> session) { | 
|  | 427   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | 428 | 
|  | 429   bool success = DisplayStepMainWorker(session); | 
|  | 430 | 
|  | 431   // On failure, clean up right away. | 
|  | 432   if (!success) | 
|  | 433     CleanupSession(session->notification_id); | 
|  | 434   // On success, clean up will happen via Windows callback to event handlers. | 
|  | 435 } | 
|  | 436 | 
|  | 437 void NotificationPlatformBridgeWin::OnClickEvent( | 
|  | 438     const std::string& notification_id, | 
|  | 439     const std::string& profile_id) { | 
|  | 440   if (!session_map_.count(notification_id)) | 
|  | 441     return; | 
|  | 442   scoped_refptr<NotificationToastSession> session = | 
|  | 443       session_map_[notification_id]; | 
|  | 444   DCHECK(session->notification_id == notification_id); | 
|  | 445   DCHECK(session->profile_id == profile_id); | 
|  | 446 | 
|  | 447   session->notification.delegate()->Click(); | 
|  | 448   // session->notification.delegate()->SettingsClick(); | 
|  | 449 } | 
|  | 450 | 
|  | 451 void NotificationPlatformBridgeWin::OnCloseEvent( | 
|  | 452     const std::string& notification_id, | 
|  | 453     const std::string& profile_id) { | 
|  | 454   if (!session_map_.count(notification_id)) | 
|  | 455     return; | 
|  | 456   scoped_refptr<NotificationToastSession> session = | 
|  | 457       session_map_[notification_id]; | 
|  | 458   DCHECK(session->notification_id == notification_id); | 
|  | 459   DCHECK(session->profile_id == profile_id); | 
|  | 460 | 
|  | 461   // TODO(huangs): Distinguish user close vs. timeout close. | 
|  | 462   session->notification.delegate()->Close(true /* by_user */); | 
|  | 463 } | 
|  | 464 | 
|  | 465 void NotificationPlatformBridgeWin::CleanupSession( | 
|  | 466     std::string notification_id) { | 
|  | 467   session_map_.erase(notification_id); | 
|  | 468 } | 
| OLD | NEW | 
|---|