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