1 // Copyright (c) 2012 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/extensions/api/notifications/notifications_api.h" 6 7 #include "base/callback.h" 8 #include "base/guid.h" 9 #include "base/rand_util.h" 10 #include "base/strings/string_number_conversions.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "base/time/time.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/notifications/desktop_notification_service.h" 15 #include "chrome/browser/notifications/desktop_notification_service_factory.h" 16 #include "chrome/browser/notifications/notification.h" 17 #include "chrome/browser/notifications/notification_conversion_helper.h" 18 #include "chrome/browser/notifications/notification_ui_manager.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/common/chrome_version_info.h" 21 #include "chrome/common/extensions/api/notifications/notification_style.h" 22 #include "content/public/browser/render_process_host.h" 23 #include "content/public/browser/render_view_host.h" 24 #include "content/public/browser/web_contents.h" 25 #include "extensions/browser/event_router.h" 26 #include "extensions/common/extension.h" 27 #include "extensions/common/features/feature.h" 28 #include "third_party/skia/include/core/SkBitmap.h" 29 #include "ui/base/layout.h" 30 #include "ui/gfx/image/image.h" 31 #include "ui/gfx/image/image_skia.h" 32 #include "ui/gfx/image/image_skia_rep.h" 33 #include "ui/message_center/message_center_style.h" 34 #include "ui/message_center/notifier_settings.h" 35 #include "url/gurl.h" 36 37 namespace extensions { 38 39 namespace notifications = api::notifications; 40 41 namespace { 42 43 const char kMissingRequiredPropertiesForCreateNotification[] = 44 "Some of the required properties are missing: type, iconUrl, title and " 45 "message."; 46 const char kUnableToDecodeIconError[] = 47 "Unable to successfully use the provided image."; 48 const char kUnexpectedProgressValueForNonProgressType[] = 49 "The progress value should not be specified for non-progress notification"; 50 const char kInvalidProgressValue[] = 51 "The progress value should range from 0 to 100"; 52 const char kExtraListItemsProvided[] = 53 "List items provided for notification type != list"; 54 const char kExtraImageProvided[] = 55 "Image resource provided for notification type != image"; 56 57 // Given an extension id and another id, returns an id that is unique 58 // relative to other extensions. 59 std::string CreateScopedIdentifier(const std::string& extension_id, 60 const std::string& id) { 61 return extension_id + "-" + id; 62 } 63 64 // Removes the unique internal identifier to send the ID as the 65 // extension expects it. 66 std::string StripScopeFromIdentifier(const std::string& extension_id, 67 const std::string& id) { 68 size_t index_of_separator = extension_id.length() + 1; 69 DCHECK_LT(index_of_separator, id.length()); 70 71 return id.substr(index_of_separator); 72 } 73 74 class NotificationsApiDelegate : public NotificationDelegate { 75 public: 76 NotificationsApiDelegate(ChromeAsyncExtensionFunction* api_function, 77 Profile* profile, 78 const std::string& extension_id, 79 const std::string& id) 80 : api_function_(api_function), 81 profile_(profile), 82 extension_id_(extension_id), 83 id_(id), 84 scoped_id_(CreateScopedIdentifier(extension_id, id)) { 85 DCHECK(api_function_.get()); 86 } 87 88 virtual void Display() OVERRIDE { } 89 90 virtual void Error() OVERRIDE {} 91 92 virtual void Close(bool by_user) OVERRIDE { 93 EventRouter::UserGestureState gesture = 94 by_user ? EventRouter::USER_GESTURE_ENABLED 95 : EventRouter::USER_GESTURE_NOT_ENABLED; 96 scoped_ptr<base::ListValue> args(CreateBaseEventArgs()); 97 args->Append(new base::FundamentalValue(by_user)); 98 SendEvent(notifications::OnClosed::kEventName, gesture, args.Pass()); 99 } 100 101 virtual void Click() OVERRIDE { 102 scoped_ptr<base::ListValue> args(CreateBaseEventArgs()); 103 SendEvent(notifications::OnClicked::kEventName, 104 EventRouter::USER_GESTURE_ENABLED, 105 args.Pass()); 106 } 107 108 virtual bool HasClickedListener() OVERRIDE { 109 return EventRouter::Get(profile_)->HasEventListener( 110 notifications::OnClicked::kEventName); 111 } 112 113 virtual void ButtonClick(int index) OVERRIDE { 114 scoped_ptr<base::ListValue> args(CreateBaseEventArgs()); 115 args->Append(new base::FundamentalValue(index)); 116 SendEvent(notifications::OnButtonClicked::kEventName, 117 EventRouter::USER_GESTURE_ENABLED, 118 args.Pass()); 119 } 120 121 virtual std::string id() const OVERRIDE { 122 return scoped_id_; 123 } 124 125 virtual content::WebContents* GetWebContents() const OVERRIDE { 126 // We're holding a reference to api_function_, so we know it'll be valid 127 // until ReleaseRVH is called, and api_function_ (as a 128 // AsyncExtensionFunction) will zero out its copy of render_view_host 129 // when the RVH goes away. 130 if (!api_function_.get()) 131 return NULL; 132 content::RenderViewHost* rvh = api_function_->render_view_host(); 133 if (!rvh) 134 return NULL; 135 return content::WebContents::FromRenderViewHost(rvh); 136 } 137 138 virtual void ReleaseRenderViewHost() OVERRIDE { 139 api_function_ = NULL; 140 } 141 142 private: 143 virtual ~NotificationsApiDelegate() {} 144 145 void SendEvent(const std::string& name, 146 EventRouter::UserGestureState user_gesture, 147 scoped_ptr<base::ListValue> args) { 148 scoped_ptr<Event> event(new Event(name, args.Pass())); 149 event->user_gesture = user_gesture; 150 EventRouter::Get(profile_)->DispatchEventToExtension(extension_id_, 151 event.Pass()); 152 } 153 154 scoped_ptr<base::ListValue> CreateBaseEventArgs() { 155 scoped_ptr<base::ListValue> args(new base::ListValue()); 156 args->Append(new base::StringValue(id_)); 157 return args.Pass(); 158 } 159 160 scoped_refptr<ChromeAsyncExtensionFunction> api_function_; 161 Profile* profile_; 162 const std::string extension_id_; 163 const std::string id_; 164 const std::string scoped_id_; 165 166 DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate); 167 }; 168 169 } // namespace 170 171 bool NotificationsApiFunction::IsNotificationsApiAvailable() { 172 // We need to check this explicitly rather than letting 173 // _permission_features.json enforce it, because we're sharing the 174 // chrome.notifications permissions namespace with WebKit notifications. 175 return extension()->is_platform_app() || extension()->is_extension(); 176 } 177 178 NotificationsApiFunction::NotificationsApiFunction() { 179 } 180 181 NotificationsApiFunction::~NotificationsApiFunction() { 182 } 183 184 bool NotificationsApiFunction::CreateNotification( 185 const std::string& id, 186 api::notifications::NotificationOptions* options) { 187 // First, make sure the required fields exist: type, title, message, icon. 188 // These fields are defined as optional in IDL such that they can be used as 189 // optional for notification updates. But for notification creations, they 190 // should be present. 191 if (options->type == api::notifications::TEMPLATE_TYPE_NONE || 192 !options->icon_url || !options->title || !options->message) { 193 SetError(kMissingRequiredPropertiesForCreateNotification); 194 return false; 195 } 196 197 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes(); 198 199 float image_scale = 200 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back()); 201 202 // Extract required fields: type, title, message, and icon. 203 message_center::NotificationType type = 204 MapApiTemplateTypeToType(options->type); 205 const base::string16 title(base::UTF8ToUTF16(*options->title)); 206 const base::string16 message(base::UTF8ToUTF16(*options->message)); 207 gfx::Image icon; 208 209 if (!NotificationConversionHelper::NotificationBitmapToGfxImage( 210 image_scale, 211 bitmap_sizes.icon_size, 212 options->icon_bitmap.get(), 213 &icon)) { 214 SetError(kUnableToDecodeIconError); 215 return false; 216 } 217 218 // Then, handle any optional data that's been provided. 219 message_center::RichNotificationData optional_fields; 220 if (options->app_icon_mask_url.get()) { 221 if (!NotificationConversionHelper::NotificationBitmapToGfxImage( 222 image_scale, 223 bitmap_sizes.app_icon_mask_size, 224 options->app_icon_mask_bitmap.get(), 225 &optional_fields.small_image)) { 226 SetError(kUnableToDecodeIconError); 227 return false; 228 } 229 } 230 231 if (options->priority.get()) 232 optional_fields.priority = *options->priority; 233 234 if (options->event_time.get()) 235 optional_fields.timestamp = base::Time::FromJsTime(*options->event_time); 236 237 if (options->buttons.get()) { 238 // Currently we allow up to 2 buttons. 239 size_t number_of_buttons = options->buttons->size(); 240 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons; 241 242 for (size_t i = 0; i < number_of_buttons; i++) { 243 message_center::ButtonInfo info( 244 base::UTF8ToUTF16((*options->buttons)[i]->title)); 245 NotificationConversionHelper::NotificationBitmapToGfxImage( 246 image_scale, 247 bitmap_sizes.button_icon_size, 248 (*options->buttons)[i]->icon_bitmap.get(), 249 &info.icon); 250 optional_fields.buttons.push_back(info); 251 } 252 } 253 254 if (options->context_message) { 255 optional_fields.context_message = 256 base::UTF8ToUTF16(*options->context_message); 257 } 258 259 bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage( 260 image_scale, 261 bitmap_sizes.image_size, 262 options->image_bitmap.get(), 263 &optional_fields.image); 264 // We should have an image if and only if the type is an image type. 265 if (has_image != (type == message_center::NOTIFICATION_TYPE_IMAGE)) { 266 SetError(kExtraImageProvided); 267 return false; 268 } 269 270 // We should have list items if and only if the type is a multiple type. 271 bool has_list_items = options->items.get() && options->items->size() > 0; 272 if (has_list_items != (type == message_center::NOTIFICATION_TYPE_MULTIPLE)) { 273 SetError(kExtraListItemsProvided); 274 return false; 275 } 276 277 if (options->progress.get() != NULL) { 278 // We should have progress if and only if the type is a progress type. 279 if (type != message_center::NOTIFICATION_TYPE_PROGRESS) { 280 SetError(kUnexpectedProgressValueForNonProgressType); 281 return false; 282 } 283 optional_fields.progress = *options->progress; 284 // Progress value should range from 0 to 100. 285 if (optional_fields.progress < 0 || optional_fields.progress > 100) { 286 SetError(kInvalidProgressValue); 287 return false; 288 } 289 } 290 291 if (has_list_items) { 292 using api::notifications::NotificationItem; 293 std::vector<linked_ptr<NotificationItem> >::iterator i; 294 for (i = options->items->begin(); i != options->items->end(); ++i) { 295 message_center::NotificationItem item( 296 base::UTF8ToUTF16(i->get()->title), 297 base::UTF8ToUTF16(i->get()->message)); 298 optional_fields.items.push_back(item); 299 } 300 } 301 302 if (options->is_clickable.get()) 303 optional_fields.clickable = *options->is_clickable; 304 305 NotificationsApiDelegate* api_delegate(new NotificationsApiDelegate( 306 this, GetProfile(), extension_->id(), id)); // ownership is passed to 307 // Notification 308 Notification notification(type, 309 extension_->url(), 310 title, 311 message, 312 icon, 313 blink::WebTextDirectionDefault, 314 message_center::NotifierId( 315 message_center::NotifierId::APPLICATION, 316 extension_->id()), 317 base::UTF8ToUTF16(extension_->name()), 318 base::UTF8ToUTF16(api_delegate->id()), 319 optional_fields, 320 api_delegate); 321 322 g_browser_process->notification_ui_manager()->Add(notification, GetProfile()); 323 return true; 324 } 325 326 bool NotificationsApiFunction::UpdateNotification( 327 const std::string& id, 328 api::notifications::NotificationOptions* options, 329 Notification* notification) { 330 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes(); 331 float image_scale = 332 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back()); 333 334 // Update optional fields if provided. 335 if (options->type != api::notifications::TEMPLATE_TYPE_NONE) 336 notification->set_type(MapApiTemplateTypeToType(options->type)); 337 if (options->title) 338 notification->set_title(base::UTF8ToUTF16(*options->title)); 339 if (options->message) 340 notification->set_message(base::UTF8ToUTF16(*options->message)); 341 342 // TODO(dewittj): Return error if this fails. 343 if (options->icon_bitmap) { 344 gfx::Image icon; 345 NotificationConversionHelper::NotificationBitmapToGfxImage( 346 image_scale, bitmap_sizes.icon_size, options->icon_bitmap.get(), &icon); 347 notification->set_icon(icon); 348 } 349 350 gfx::Image app_icon_mask; 351 if (NotificationConversionHelper::NotificationBitmapToGfxImage( 352 image_scale, 353 bitmap_sizes.app_icon_mask_size, 354 options->app_icon_mask_bitmap.get(), 355 &app_icon_mask)) { 356 notification->set_small_image(app_icon_mask); 357 } 358 359 if (options->priority) 360 notification->set_priority(*options->priority); 361 362 if (options->event_time) 363 notification->set_timestamp(base::Time::FromJsTime(*options->event_time)); 364 365 if (options->buttons) { 366 // Currently we allow up to 2 buttons. 367 size_t number_of_buttons = options->buttons->size(); 368 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons; 369 370 std::vector<message_center::ButtonInfo> buttons; 371 for (size_t i = 0; i < number_of_buttons; i++) { 372 message_center::ButtonInfo button( 373 base::UTF8ToUTF16((*options->buttons)[i]->title)); 374 NotificationConversionHelper::NotificationBitmapToGfxImage( 375 image_scale, 376 bitmap_sizes.button_icon_size, 377 (*options->buttons)[i]->icon_bitmap.get(), 378 &button.icon); 379 buttons.push_back(button); 380 } 381 notification->set_buttons(buttons); 382 } 383 384 if (options->context_message) { 385 notification->set_context_message( 386 base::UTF8ToUTF16(*options->context_message)); 387 } 388 389 gfx::Image image; 390 bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage( 391 image_scale, 392 bitmap_sizes.image_size, 393 options->image_bitmap.get(), 394 &image); 395 if (has_image) { 396 // We should have an image if and only if the type is an image type. 397 if (notification->type() != message_center::NOTIFICATION_TYPE_IMAGE) { 398 SetError(kExtraImageProvided); 399 return false; 400 } 401 notification->set_image(image); 402 } 403 404 if (options->progress) { 405 // We should have progress if and only if the type is a progress type. 406 if (notification->type() != message_center::NOTIFICATION_TYPE_PROGRESS) { 407 SetError(kUnexpectedProgressValueForNonProgressType); 408 return false; 409 } 410 int progress = *options->progress; 411 // Progress value should range from 0 to 100. 412 if (progress < 0 || progress > 100) { 413 SetError(kInvalidProgressValue); 414 return false; 415 } 416 notification->set_progress(progress); 417 } 418 419 if (options->items.get() && options->items->size() > 0) { 420 // We should have list items if and only if the type is a multiple type. 421 if (notification->type() != message_center::NOTIFICATION_TYPE_MULTIPLE) { 422 SetError(kExtraListItemsProvided); 423 return false; 424 } 425 426 std::vector<message_center::NotificationItem> items; 427 using api::notifications::NotificationItem; 428 std::vector<linked_ptr<NotificationItem> >::iterator i; 429 for (i = options->items->begin(); i != options->items->end(); ++i) { 430 message_center::NotificationItem item( 431 base::UTF8ToUTF16(i->get()->title), 432 base::UTF8ToUTF16(i->get()->message)); 433 items.push_back(item); 434 } 435 notification->set_items(items); 436 } 437 438 // Then override if it's already set. 439 if (options->is_clickable.get()) 440 notification->set_clickable(*options->is_clickable); 441 442 g_browser_process->notification_ui_manager()->Update(*notification, 443 GetProfile()); 444 return true; 445 } 446 447 bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const { 448 DesktopNotificationService* service = 449 DesktopNotificationServiceFactory::GetForProfile(GetProfile()); 450 return service->IsNotifierEnabled(message_center::NotifierId( 451 message_center::NotifierId::APPLICATION, extension_->id())); 452 } 453 454 bool NotificationsApiFunction::IsNotificationsApiEnabled() const { 455 return CanRunWhileDisabled() || AreExtensionNotificationsAllowed(); 456 } 457 458 bool NotificationsApiFunction::CanRunWhileDisabled() const { 459 return false; 460 } 461 462 bool NotificationsApiFunction::RunAsync() { 463 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) { 464 return RunNotificationsApi(); 465 } else { 466 SendResponse(false); 467 return true; 468 } 469 } 470 471 message_center::NotificationType 472 NotificationsApiFunction::MapApiTemplateTypeToType( 473 api::notifications::TemplateType type) { 474 switch (type) { 475 case api::notifications::TEMPLATE_TYPE_NONE: 476 case api::notifications::TEMPLATE_TYPE_BASIC: 477 return message_center::NOTIFICATION_TYPE_BASE_FORMAT; 478 case api::notifications::TEMPLATE_TYPE_IMAGE: 479 return message_center::NOTIFICATION_TYPE_IMAGE; 480 case api::notifications::TEMPLATE_TYPE_LIST: 481 return message_center::NOTIFICATION_TYPE_MULTIPLE; 482 case api::notifications::TEMPLATE_TYPE_PROGRESS: 483 return message_center::NOTIFICATION_TYPE_PROGRESS; 484 default: 485 // Gracefully handle newer application code that is running on an older 486 // runtime that doesn't recognize the requested template. 487 return message_center::NOTIFICATION_TYPE_BASE_FORMAT; 488 } 489 } 490 491 NotificationsCreateFunction::NotificationsCreateFunction() { 492 } 493 494 NotificationsCreateFunction::~NotificationsCreateFunction() { 495 } 496 497 bool NotificationsCreateFunction::RunNotificationsApi() { 498 params_ = api::notifications::Create::Params::Create(*args_); 499 EXTENSION_FUNCTION_VALIDATE(params_.get()); 500 501 const std::string extension_id(extension_->id()); 502 std::string notification_id; 503 if (!params_->notification_id.empty()) { 504 // If the caller provided a notificationId, use that. 505 notification_id = params_->notification_id; 506 } else { 507 // Otherwise, use a randomly created GUID. In case that GenerateGUID returns 508 // the empty string, simply generate a random string. 509 notification_id = base::GenerateGUID(); 510 if (notification_id.empty()) 511 notification_id = base::RandBytesAsString(16); 512 } 513 514 SetResult(new base::StringValue(notification_id)); 515 516 // TODO(dewittj): Add more human-readable error strings if this fails. 517 if (!CreateNotification(notification_id, ¶ms_->options)) 518 return false; 519 520 SendResponse(true); 521 522 return true; 523 } 524 525 NotificationsUpdateFunction::NotificationsUpdateFunction() { 526 } 527 528 NotificationsUpdateFunction::~NotificationsUpdateFunction() { 529 } 530 531 bool NotificationsUpdateFunction::RunNotificationsApi() { 532 params_ = api::notifications::Update::Params::Create(*args_); 533 EXTENSION_FUNCTION_VALIDATE(params_.get()); 534 535 // We are in update. If the ID doesn't exist, succeed but call the callback 536 // with "false". 537 const Notification* matched_notification = 538 g_browser_process->notification_ui_manager()->FindById( 539 CreateScopedIdentifier(extension_->id(), params_->notification_id)); 540 if (!matched_notification) { 541 SetResult(new base::FundamentalValue(false)); 542 SendResponse(true); 543 return true; 544 } 545 546 // Copy the existing notification to get a writable version of it. 547 Notification notification = *matched_notification; 548 549 // If we have trouble updating the notification (could be improper use of API 550 // or some other reason), mark the function as failed, calling the callback 551 // with false. 552 // TODO(dewittj): Add more human-readable error strings if this fails. 553 bool could_update_notification = UpdateNotification( 554 params_->notification_id, ¶ms_->options, ¬ification); 555 SetResult(new base::FundamentalValue(could_update_notification)); 556 if (!could_update_notification) 557 return false; 558 559 // No trouble, created the notification, send true to the callback and 560 // succeed. 561 SendResponse(true); 562 return true; 563 } 564 565 NotificationsClearFunction::NotificationsClearFunction() { 566 } 567 568 NotificationsClearFunction::~NotificationsClearFunction() { 569 } 570 571 bool NotificationsClearFunction::RunNotificationsApi() { 572 params_ = api::notifications::Clear::Params::Create(*args_); 573 EXTENSION_FUNCTION_VALIDATE(params_.get()); 574 575 bool cancel_result = g_browser_process->notification_ui_manager()->CancelById( 576 CreateScopedIdentifier(extension_->id(), params_->notification_id)); 577 578 SetResult(new base::FundamentalValue(cancel_result)); 579 SendResponse(true); 580 581 return true; 582 } 583 584 NotificationsGetAllFunction::NotificationsGetAllFunction() {} 585 586 NotificationsGetAllFunction::~NotificationsGetAllFunction() {} 587 588 bool NotificationsGetAllFunction::RunNotificationsApi() { 589 NotificationUIManager* notification_ui_manager = 590 g_browser_process->notification_ui_manager(); 591 std::set<std::string> notification_ids = 592 notification_ui_manager->GetAllIdsByProfileAndSourceOrigin( 593 GetProfile(), extension_->url()); 594 595 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); 596 597 for (std::set<std::string>::iterator iter = notification_ids.begin(); 598 iter != notification_ids.end(); iter++) { 599 result->SetBooleanWithoutPathExpansion( 600 StripScopeFromIdentifier(extension_->id(), *iter), true); 601 } 602 603 SetResult(result.release()); 604 SendResponse(true); 605 606 return true; 607 } 608 609 NotificationsGetPermissionLevelFunction:: 610 NotificationsGetPermissionLevelFunction() {} 611 612 NotificationsGetPermissionLevelFunction:: 613 ~NotificationsGetPermissionLevelFunction() {} 614 615 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const { 616 return true; 617 } 618 619 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() { 620 api::notifications::PermissionLevel result = 621 AreExtensionNotificationsAllowed() 622 ? api::notifications::PERMISSION_LEVEL_GRANTED 623 : api::notifications::PERMISSION_LEVEL_DENIED; 624 625 SetResult(new base::StringValue(api::notifications::ToString(result))); 626 SendResponse(true); 627 628 return true; 629 } 630 631 } // namespace extensions 632