Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(13)

Side by Side Diff: chrome/browser/notifications/notification_platform_bridge_win.cc

Issue 2033093003: [Notification] Make HTML5 Notification use ActionCenter on Windows 10, behind Flags. Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Sync and merge. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698