summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ash/ash.gyp3
-rw-r--r--ash/ash_strings.grd12
-rw-r--r--ash/shell/window_type_launcher.cc16
-rw-r--r--ash/shell/window_type_launcher.h1
-rw-r--r--ash/system/status_area_widget.cc49
-rw-r--r--ash/system/status_area_widget.h29
-rw-r--r--ash/system/tray/system_tray.cc15
-rw-r--r--ash/system/tray/system_tray.h10
-rw-r--r--ash/system/tray/system_tray_bubble.cc14
-rw-r--r--ash/system/tray/system_tray_bubble.h2
-rw-r--r--ash/system/web_notification/web_notification_tray.cc827
-rw-r--r--ash/system/web_notification/web_notification_tray.h165
-rw-r--r--ash/system/web_notification/web_notification_tray_unittest.cc106
-rw-r--r--ash/wm/shelf_layout_manager.cc15
14 files changed, 1251 insertions, 13 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp
index 67907da..15bf18d 100644
--- a/ash/ash.gyp
+++ b/ash/ash.gyp
@@ -193,6 +193,8 @@
'system/user/tray_user.h',
'system/user/update_observer.h',
'system/user/user_observer.h',
+ 'system/web_notification/web_notification_tray.cc',
+ 'system/web_notification/web_notification_tray.h',
'tooltips/tooltip_controller.cc',
'tooltips/tooltip_controller.h',
'touch/touch_observer_hud.cc',
@@ -383,6 +385,7 @@
'screensaver/screensaver_view_unittest.cc',
'shell_unittest.cc',
'system/tray/system_tray_unittest.cc',
+ 'system/web_notification/web_notification_tray_unittest.cc',
'test/ash_test_base.cc',
'test/ash_test_base.h',
'test/ash_unittests.cc',
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index e28fbbf..702edbf 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -393,6 +393,18 @@ Press Search key to cancel.
<message name="IDS_ASH_STATUS_TRAY_PREVIOUS_MENU" desc="The accessible text for header entries for detailed versions of status tray items.">
Previous menu
</message>
+ <message name="IDS_ASH_WEB_NOTFICATION_TRAY_EXTENSIONS_DISABLE" desc="The menu entry for disabling extensions from a notification.">
+ Disable extension
+ </message>
+ <message name="IDS_ASH_WEB_NOTFICATION_TRAY_SITE_DISABLE" desc="The menu entry for disabling notification from a site.">
+ Disable notifications from <ph name="site">$1<ex>mail.google.com</ex></ph>
+ </message>
+ <message name="IDS_ASH_WEB_NOTFICATION_TRAY_SETTINGS" desc="The menu entry or button for visiting the appropriate settings page.">
+ Settings...
+ </message>
+ <message name="IDS_ASH_WEB_NOTFICATION_TRAY_CLOSE_ALL" desc="The button for closing all notifications.">
+ Close All
+ </message>
</messages>
</release>
</grit>
diff --git a/ash/shell/window_type_launcher.cc b/ash/shell/window_type_launcher.cc
index 243273a..b783518 100644
--- a/ash/shell/window_type_launcher.cc
+++ b/ash/shell/window_type_launcher.cc
@@ -11,6 +11,8 @@
#include "ash/shell/toplevel_window.h"
#include "ash/shell_delegate.h"
#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/web_notification/web_notification_tray.h"
#include "ash/wm/shadow_types.h"
#include "base/bind.h"
#include "base/time.h"
@@ -223,7 +225,10 @@ WindowTypeLauncher::WindowTypeLauncher()
this, ASCIIToUTF16("Show/Hide a Window")))),
ALLOW_THIS_IN_INITIALIZER_LIST(show_screensaver_(
new views::NativeTextButton(
- this, ASCIIToUTF16("Show the Screensaver [for 5 seconds]")))) {
+ this, ASCIIToUTF16("Show the Screensaver [for 5 seconds]")))),
+ ALLOW_THIS_IN_INITIALIZER_LIST(show_web_notification_(
+ new views::NativeTextButton(
+ this, ASCIIToUTF16("Show a web/app notification")))) {
views::GridLayout* layout = new views::GridLayout(this);
layout->SetInsets(5, 5, 5, 5);
SetLayoutManager(layout);
@@ -247,6 +252,7 @@ WindowTypeLauncher::WindowTypeLauncher()
AddViewToLayout(layout, examples_button_);
AddViewToLayout(layout, show_hide_window_button_);
AddViewToLayout(layout, show_screensaver_);
+ AddViewToLayout(layout, show_web_notification_);
#if !defined(OS_MACOSX)
set_context_menu_controller(this);
#endif
@@ -320,6 +326,14 @@ void WindowTypeLauncher::ButtonPressed(views::Button* sender,
base::Bind(&ash::CloseScreensaver),
base::TimeDelta::FromSeconds(5));
+ } else if (sender == show_web_notification_) {
+ ash::Shell::GetInstance()->status_area_widget()->
+ web_notification_tray()->AddNotification(
+ "id0",
+ ASCIIToUTF16("Test Shell Web Notification"),
+ ASCIIToUTF16("Notification message body."),
+ ASCIIToUTF16("www.testshell.org"),
+ "" /* extension id */);
}
#if !defined(OS_MACOSX)
else if (sender == examples_button_) {
diff --git a/ash/shell/window_type_launcher.h b/ash/shell/window_type_launcher.h
index 4dcc5ab..69e7a93 100644
--- a/ash/shell/window_type_launcher.h
+++ b/ash/shell/window_type_launcher.h
@@ -78,6 +78,7 @@ class WindowTypeLauncher : public views::WidgetDelegateView,
views::NativeTextButton* examples_button_;
views::NativeTextButton* show_hide_window_button_;
views::NativeTextButton* show_screensaver_;
+ views::NativeTextButton* show_web_notification_;
#if !defined(OS_MACOSX)
scoped_ptr<views::MenuRunner> menu_runner_;
#endif
diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc
index 9c0a312..b3eb72f 100644
--- a/ash/system/status_area_widget.cc
+++ b/ash/system/status_area_widget.cc
@@ -13,6 +13,7 @@
#include "ash/system/status_area_widget_delegate.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/web_notification/web_notification_tray.h"
#include "base/i18n/time_formatting.h"
#include "base/utf_string_conversions.h"
#include "ui/aura/window.h"
@@ -282,7 +283,8 @@ namespace internal {
StatusAreaWidget::StatusAreaWidget()
: widget_delegate_(new internal::StatusAreaWidgetDelegate),
- system_tray_(NULL) {
+ system_tray_(NULL),
+ web_notification_tray_(NULL) {
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.delegate = widget_delegate_;
@@ -300,6 +302,7 @@ StatusAreaWidget::~StatusAreaWidget() {
}
void StatusAreaWidget::CreateTrayViews(ShellDelegate* shell_delegate) {
+ AddWebNotificationTray(new WebNotificationTray(this));
AddSystemTray(new SystemTray(), shell_delegate);
}
@@ -309,6 +312,8 @@ void StatusAreaWidget::Shutdown() {
// in the destructor if Shutdown() is not called (e.g. in tests).
delete system_tray_;
system_tray_ = NULL;
+ delete web_notification_tray_;
+ web_notification_tray_ = NULL;
}
void StatusAreaWidget::AddSystemTray(SystemTray* system_tray,
@@ -327,9 +332,51 @@ void StatusAreaWidget::AddSystemTray(SystemTray* system_tray,
system_tray->CreateItems(); // Called after delegate is created.
}
+void StatusAreaWidget::AddWebNotificationTray(
+ WebNotificationTray* web_notification_tray) {
+ web_notification_tray_ = web_notification_tray;
+ widget_delegate_->AddTray(web_notification_tray);
+}
+
void StatusAreaWidget::SetShelfAlignment(ShelfAlignment alignment) {
widget_delegate_->set_alignment(alignment);
widget_delegate_->UpdateLayout();
+ if (system_tray_)
+ system_tray_->SetShelfAlignment(alignment);
+ if (web_notification_tray_)
+ web_notification_tray_->SetShelfAlignment(alignment);
+}
+
+void StatusAreaWidget::SetPaintsBackground(
+ bool value,
+ internal::BackgroundAnimator::ChangeType change_type) {
+ if (system_tray_)
+ system_tray_->SetPaintsBackground(value, change_type);
+ if (web_notification_tray_)
+ web_notification_tray_->SetPaintsBackground(value, change_type);
+}
+
+void StatusAreaWidget::ShowWebNotificationBubble(UserAction user_action) {
+ if (system_tray_ && system_tray_->IsBubbleVisible()) {
+ // User actions should always hide the system tray bubble first.
+ DCHECK(user_action != USER_ACTION);
+ // Don't immediately show the web notification bubble if the system tray
+ // bubble is visible.
+ return;
+ }
+ DCHECK(web_notification_tray_);
+ web_notification_tray_->ShowBubble();
+ // Disable showing system notifications while viewing web notifications.
+ if (system_tray_)
+ system_tray_->SetHideNotifications(true);
+}
+
+void StatusAreaWidget::HideWebNotificationBubble() {
+ DCHECK(web_notification_tray_);
+ web_notification_tray_->HideBubble();
+ // Show any hidden or suppressed system notifications.
+ if (system_tray_)
+ system_tray_->SetHideNotifications(false);
}
} // namespace internal
diff --git a/ash/system/status_area_widget.h b/ash/system/status_area_widget.h
index 8e56688..5b66862 100644
--- a/ash/system/status_area_widget.h
+++ b/ash/system/status_area_widget.h
@@ -7,6 +7,7 @@
#pragma once
#include "ash/ash_export.h"
+#include "ash/launcher/background_animator.h"
#include "ash/wm/shelf_auto_hide_behavior.h"
#include "ui/views/widget/widget.h"
@@ -15,6 +16,7 @@ namespace ash {
class ShellDelegate;
class SystemTray;
class SystemTrayDelegate;
+class WebNotificationTray;
namespace internal {
@@ -22,30 +24,51 @@ class StatusAreaWidgetDelegate;
class ASH_EXPORT StatusAreaWidget : public views::Widget {
public:
+ enum UserAction {
+ NON_USER_ACTION,
+ USER_ACTION
+ };
+
StatusAreaWidget();
virtual ~StatusAreaWidget();
- // Creates the SystemTray.
+ // Creates the SystemTray and the WebNotificationTray.
void CreateTrayViews(ShellDelegate* shell_delegate);
- // Destroys the system tray. Called before tearing down the windows to avoid
- // shutdown ordering issues.
+ // Destroys the system tray and web notification tray. Called before
+ // tearing down the windows to avoid shutdown ordering issues.
void Shutdown();
+ // Update the alignment of the widget and tray views.
void SetShelfAlignment(ShelfAlignment alignment);
+ // Update whether to paint a background for each tray view.
+ void SetPaintsBackground(
+ bool value,
+ internal::BackgroundAnimator::ChangeType change_type);
+
+ // Always used to show/hide the web notification tray. These handle any logic
+ // with hiding/supressing notifications from the system tray.
+ void ShowWebNotificationBubble(UserAction user_action);
+ void HideWebNotificationBubble();
+
SystemTray* system_tray() { return system_tray_; }
SystemTrayDelegate* system_tray_delegate() {
return system_tray_delegate_.get();
}
+ WebNotificationTray* web_notification_tray() {
+ return web_notification_tray_;
+ }
private:
void AddSystemTray(SystemTray* system_tray, ShellDelegate* shell_delegate);
+ void AddWebNotificationTray(WebNotificationTray* web_notification_tray);
scoped_ptr<SystemTrayDelegate> system_tray_delegate_;
// Weak pointers to View classes that are parented to StatusAreaWidget:
internal::StatusAreaWidgetDelegate* widget_delegate_;
SystemTray* system_tray_;
+ WebNotificationTray* web_notification_tray_;
DISALLOW_COPY_AND_ASSIGN(StatusAreaWidget);
};
diff --git a/ash/system/tray/system_tray.cc b/ash/system/tray/system_tray.cc
index 707c962..0b6e2df 100644
--- a/ash/system/tray/system_tray.cc
+++ b/ash/system/tray/system_tray.cc
@@ -138,7 +138,8 @@ SystemTray::SystemTray()
update_observer_(NULL),
user_observer_(NULL),
should_show_launcher_(false),
- default_bubble_height_(0) {
+ default_bubble_height_(0),
+ hide_notifications_(false) {
tray_container_ = new internal::SystemTrayContainer;
tray_container_->SetLayoutManager(new views::BoxLayout(
views::BoxLayout::kHorizontal, 0, 0, 0));
@@ -298,6 +299,16 @@ void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
}
}
+void SystemTray::SetHideNotifications(bool hide_notifications) {
+ if (notification_bubble_.get())
+ notification_bubble_->SetVisible(!hide_notifications);
+ hide_notifications_ = hide_notifications;
+}
+
+bool SystemTray::IsBubbleVisible() const {
+ return bubble_.get() && bubble_->IsVisible();
+}
+
bool SystemTray::CloseBubbleForTest() const {
if (!bubble_.get())
return false;
@@ -462,6 +473,8 @@ void SystemTray::UpdateNotificationBubble() {
if (arrow_offset >= 0)
init_params.arrow_offset = arrow_offset;
notification_bubble_->InitView(init_params);
+ if (hide_notifications_)
+ notification_bubble_->SetVisible(false);
}
void SystemTray::UpdateNotificationAnchor() {
diff --git a/ash/system/tray/system_tray.h b/ash/system/tray/system_tray.h
index f6cf1ca..d79fc93 100644
--- a/ash/system/tray/system_tray.h
+++ b/ash/system/tray/system_tray.h
@@ -96,6 +96,12 @@ class ASH_EXPORT SystemTray : public internal::TrayBackgroundView {
// Updates the items when the shelf alignment changes.
void UpdateAfterShelfAlignmentChange(ShelfAlignment alignment);
+ // Temporarily hides/unhides the notification bubble.
+ void SetHideNotifications(bool hidden);
+
+ // Returns true if the primary bubble is visible.
+ bool IsBubbleVisible() const;
+
// Returns true if the launcher should show.
bool should_show_launcher() const {
return bubble_.get() && should_show_launcher_;
@@ -241,6 +247,10 @@ class ASH_EXPORT SystemTray : public internal::TrayBackgroundView {
// views directly (e.g. from a notification) we know what height to use.
int default_bubble_height_;
+ // Set to true when system notifications should be hidden (e.g. web
+ // notification bubble is visible).
+ bool hide_notifications_;
+
DISALLOW_COPY_AND_ASSIGN(SystemTray);
};
diff --git a/ash/system/tray/system_tray_bubble.cc b/ash/system/tray/system_tray_bubble.cc
index df7c2d5..6888cc4 100644
--- a/ash/system/tray/system_tray_bubble.cc
+++ b/ash/system/tray/system_tray_bubble.cc
@@ -336,6 +336,7 @@ void SystemTrayBubble::InitView(const InitParams& init_params) {
void SystemTrayBubble::BubbleViewDestroyed() {
DestroyItemViews();
+ bubble_view_ = NULL;
}
gfx::Rect SystemTrayBubble::GetAnchorRect() const {
@@ -422,6 +423,19 @@ void SystemTrayBubble::Close() {
bubble_widget_->Close();
}
+void SystemTrayBubble::SetVisible(bool is_visible) {
+ if (!bubble_widget_)
+ return;
+ if (is_visible)
+ bubble_widget_->Show();
+ else
+ bubble_widget_->Hide();
+}
+
+bool SystemTrayBubble::IsVisible() {
+ return bubble_widget_ && bubble_widget_->IsVisible();
+}
+
void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
it != items_.end();
diff --git a/ash/system/tray/system_tray_bubble.h b/ash/system/tray/system_tray_bubble.h
index bdc138e..5373601 100644
--- a/ash/system/tray/system_tray_bubble.h
+++ b/ash/system/tray/system_tray_bubble.h
@@ -80,6 +80,8 @@ class SystemTrayBubble : public aura::EventFilter,
void StopAutoCloseTimer();
void RestartAutoCloseTimer();
void Close();
+ void SetVisible(bool is_visible);
+ bool IsVisible();
private:
void CreateItemViews(user::LoginStatus login_status);
diff --git a/ash/system/web_notification/web_notification_tray.cc b/ash/system/web_notification/web_notification_tray.cc
new file mode 100644
index 0000000..ba81fa3
--- /dev/null
+++ b/ash/system/web_notification/web_notification_tray.cc
@@ -0,0 +1,827 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/web_notification/web_notification_tray.h"
+
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/tray_bubble_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_views.h"
+#include "grit/ash_strings.h"
+#include "grit/ui_resources.h"
+#include "grit/ui_resources_standard.h"
+#include "ui/aura/event.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/menu_button.h"
+#include "ui/views/controls/button/menu_button_listener.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/painter.h"
+
+namespace {
+
+const int kTrayBorder = 4;
+const int kNotificationIconWidth = 40;
+const int kNotificationIconHeight = 25;
+const int kWebNotificationBubbleMinHeight = 80;
+const int kWebNotificationBubbleMaxHeight = 400;
+const int kWebNotificationWidth = 400;
+const int kWebNotificationButtonWidth = 32;
+
+const int kTogglePermissionCommand = 0;
+const int kToggleExtensionCommand = 1;
+const int kShowSettingsCommand = 2;
+
+// The image has three icons: 1 notifiaction, 2 notifications, and 3+.
+SkBitmap GetNotificationImage(int notification_count) {
+ SkBitmap image;
+ gfx::Image all = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_AURA_UBER_TRAY_WEB_NOTIFICATON);
+ int image_index = notification_count - 1;
+ image_index = std::max(0, std::min(image_index, 2));
+ SkIRect region = SkIRect::MakeXYWH(
+ 0, image_index * kNotificationIconHeight,
+ kNotificationIconWidth, kNotificationIconHeight);
+ all.ToSkBitmap()->extractSubset(&image, region);
+ return image;
+}
+
+} // namespace
+
+namespace ash {
+
+namespace internal {
+
+struct WebNotification {
+ WebNotification(const std::string& i,
+ const string16& t,
+ const string16& m,
+ const string16& s,
+ const std::string& e)
+ : id(i),
+ title(t),
+ message(m),
+ display_source(s),
+ extension_id(e) {
+ }
+
+ std::string id;
+ string16 title;
+ string16 message;
+ string16 display_source;
+ std::string extension_id;
+ gfx::ImageSkia image;
+};
+
+// A helper class to manage the list of notifications.
+class WebNotificationList {
+ public:
+ typedef std::list<WebNotification> Notifications;
+
+ WebNotificationList() {
+ }
+
+ void AddNotification(const std::string& id,
+ const string16& title,
+ const string16& message,
+ const string16& display_source,
+ const std::string& extension_id) {
+ Notifications::iterator iter = GetNotification(id);
+ if (iter != notifications_.end()) {
+ // Update existing notification.
+ iter->title = title;
+ iter->message = message;
+ iter->display_source = display_source;
+ iter->extension_id = extension_id;
+ } else {
+ notifications_.push_back(
+ WebNotification(id, title, message, display_source, extension_id));
+ }
+ }
+
+ void UpdateNotificationMessage(const std::string& id,
+ const string16& title,
+ const string16& message) {
+ Notifications::iterator iter = GetNotification(id);
+ if (iter == notifications_.end())
+ return;
+ iter->title = title;
+ iter->message = message;
+ }
+
+ bool RemoveNotification(const std::string& id) {
+ Notifications::iterator iter = GetNotification(id);
+ if (iter == notifications_.end())
+ return false;
+ notifications_.erase(iter);
+ return true;
+ }
+
+ void RemoveAllNotifications() {
+ notifications_.clear();
+ }
+
+ void RemoveNotificationsBySource(const std::string& id) {
+ Notifications::iterator source_iter = GetNotification(id);
+ if (source_iter == notifications_.end())
+ return;
+ string16 display_source = source_iter->display_source;
+ for (Notifications::iterator loopiter = notifications_.begin();
+ loopiter != notifications_.end(); ) {
+ Notifications::iterator curiter = loopiter++;
+ if (curiter->display_source == display_source)
+ notifications_.erase(curiter);
+ }
+ }
+
+ void RemoveNotificationsByExtension(const std::string& id) {
+ Notifications::iterator source_iter = GetNotification(id);
+ if (source_iter == notifications_.end())
+ return;
+ std::string extension_id = source_iter->extension_id;
+ for (Notifications::iterator loopiter = notifications_.begin();
+ loopiter != notifications_.end(); ) {
+ Notifications::iterator curiter = loopiter++;
+ if (curiter->extension_id == extension_id)
+ notifications_.erase(curiter);
+ }
+ }
+
+ bool SetNotificationImage(const std::string& id,
+ const gfx::ImageSkia& image) {
+ Notifications::iterator iter = GetNotification(id);
+ if (iter == notifications_.end())
+ return false;
+ iter->image = image;
+ return true;
+ }
+
+ const Notifications& notifications() const { return notifications_; }
+
+ private:
+ Notifications::iterator GetNotification(const std::string& id) {
+ for (Notifications::iterator iter = notifications_.begin();
+ iter != notifications_.end(); ++iter) {
+ if (iter->id == id)
+ return iter;
+ }
+ return notifications_.end();
+ }
+
+ Notifications notifications_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationList);
+};
+
+// A simple view for the text (title and message) of a notification.
+class WebNotificationMessageView : public views::View {
+ public:
+ explicit WebNotificationMessageView(const WebNotification& notification) {
+ views::Label* title = new views::Label(notification.title);
+ title->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ title->SetFont(title->font().DeriveFont(0, gfx::Font::BOLD));
+ views::Label* message = new views::Label(notification.message);
+ message->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ message->SetMultiLine(true);
+
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+ AddChildView(title);
+ AddChildView(message);
+ }
+
+ virtual ~WebNotificationMessageView() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationMessageView);
+};
+
+// A dropdown menu for notifications.
+class WebNotificationMenuModel : public ui::SimpleMenuModel,
+ public ui::SimpleMenuModel::Delegate {
+ public:
+ explicit WebNotificationMenuModel(WebNotificationTray* tray,
+ const WebNotification& notification)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
+ tray_(tray),
+ notification_(notification) {
+ // Add 'disable notifications' menu item.
+ if (!notification.extension_id.empty()) {
+ AddItem(kToggleExtensionCommand,
+ GetLabelForCommandId(kToggleExtensionCommand));
+ } else if (!notification.display_source.empty()) {
+ AddItem(kTogglePermissionCommand,
+ GetLabelForCommandId(kTogglePermissionCommand));
+ }
+ // Add settings menu item.
+ if (!notification.display_source.empty()) {
+ AddItem(kShowSettingsCommand,
+ GetLabelForCommandId(kShowSettingsCommand));
+ }
+ }
+
+ virtual ~WebNotificationMenuModel() {
+ }
+
+ // Overridden from ui::SimpleMenuModel:
+ virtual string16 GetLabelForCommandId(int command_id) const OVERRIDE {
+ switch (command_id) {
+ case kToggleExtensionCommand:
+ return l10n_util::GetStringUTF16(
+ IDS_ASH_WEB_NOTFICATION_TRAY_EXTENSIONS_DISABLE);
+ case kTogglePermissionCommand:
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_WEB_NOTFICATION_TRAY_SITE_DISABLE,
+ notification_.display_source);
+ case kShowSettingsCommand:
+ return l10n_util::GetStringUTF16(
+ IDS_ASH_WEB_NOTFICATION_TRAY_SETTINGS);
+ default:
+ NOTREACHED();
+ }
+ return string16();
+ }
+
+ // Overridden from ui::SimpleMenuModel::Delegate:
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
+ return false;
+ }
+
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
+ return true;
+ }
+
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE {
+ return false;
+ }
+
+ virtual void ExecuteCommand(int command_id) OVERRIDE {
+ switch (command_id) {
+ case kToggleExtensionCommand:
+ tray_->DisableByExtension(notification_.id);
+ break;
+ case kTogglePermissionCommand:
+ tray_->DisableByUrl(notification_.id);
+ break;
+ case kShowSettingsCommand:
+ tray_->ShowSettings(notification_.id);
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ private:
+ WebNotificationTray* tray_;
+ WebNotification notification_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationMenuModel);
+};
+
+// The view for a notification entry (icon + message + buttons).
+class WebNotificationView : public views::View,
+ public views::ButtonListener,
+ public views::MenuButtonListener {
+ public:
+ WebNotificationView(WebNotificationTray* tray,
+ const WebNotification& notification)
+ : tray_(tray),
+ notification_(notification),
+ icon_(NULL),
+ menu_button_(NULL),
+ close_button_(NULL) {
+ InitView(tray, notification);
+ }
+
+ virtual ~WebNotificationView() {
+ }
+
+ void InitView(WebNotificationTray* tray,
+ const WebNotification& notification) {
+ set_border(views::Border::CreateSolidSidedBorder(
+ 1, 0, 0, 0, kBorderLightColor));
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+
+ icon_ = new views::ImageView;
+ icon_->SetImage(notification.image);
+
+ WebNotificationMessageView* message_view
+ = new WebNotificationMessageView(notification);
+
+ close_button_ = new views::ImageButton(this);
+ close_button_->SetImage(
+ views::CustomButton::BS_NORMAL,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_WINDOW_CLOSE));
+
+ if (!notification.extension_id.empty() ||
+ !notification.display_source.empty()) {
+ menu_button_ = new views::MenuButton(NULL, string16(), this, true);
+ menu_button_->set_border(NULL);
+ }
+
+ views::GridLayout* layout = new views::GridLayout(this);
+ SetLayoutManager(layout);
+
+ views::ColumnSet* columns = layout->AddColumnSet(0);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2);
+
+ // Notification Icon.
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, /* resize percent */
+ views::GridLayout::FIXED,
+ kNotificationIconWidth, kNotificationIconWidth);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2);
+
+ // Notification message text.
+ columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
+ 100, /* resize percent */
+ views::GridLayout::USE_PREF, 0, 0);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2);
+
+ // Close and menu buttons.
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, /* resize percent */
+ views::GridLayout::FIXED,
+ kWebNotificationButtonWidth,
+ kWebNotificationButtonWidth);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2);
+
+ // Layout rows
+ layout->AddPaddingRow(0, kTrayPopupPaddingBetweenItems);
+
+ layout->StartRow(0, 0);
+ layout->AddView(icon_, 1, 2);
+ layout->AddView(message_view, 1, 2);
+ layout->AddView(close_button_);
+
+ layout->StartRow(0, 0);
+ if (menu_button_) {
+ layout->SkipColumns(4);
+ layout->AddView(menu_button_);
+ }
+ layout->AddPaddingRow(0, kTrayPopupPaddingBetweenItems);
+ }
+
+ // view::Views overrodes.
+ virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE {
+ tray_->OnClicked(notification_.id);
+ return true;
+ }
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const views::Event& event) OVERRIDE {
+ if (sender == close_button_)
+ tray_->RemoveNotification(notification_.id);
+ }
+
+ // Overridden from MenuButtonListener.
+ virtual void OnMenuButtonClicked(
+ View* source, const gfx::Point& point) OVERRIDE {
+ if (source != menu_button_)
+ return;
+ WebNotificationMenuModel menu_model(tray_, notification_);
+ views::MenuModelAdapter menu_model_adapter(&menu_model);
+ views::MenuRunner menu_runner(menu_model_adapter.CreateMenu());
+
+ gfx::Point screen_location;
+ views::View::ConvertPointToScreen(menu_button_, &screen_location);
+ ignore_result(menu_runner.RunMenuAt(
+ source->GetWidget()->GetTopLevelWidget(),
+ menu_button_,
+ gfx::Rect(screen_location, menu_button_->size()),
+ views::MenuItemView::TOPRIGHT,
+ views::MenuRunner::HAS_MNEMONICS));
+ }
+
+ private:
+ WebNotificationTray* tray_;
+ WebNotification notification_;
+ views::ImageView* icon_;
+ views::MenuButton* menu_button_;
+ views::ImageButton* close_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationView);
+};
+
+// The view for the buttons at the bottom of the web notification tray.
+class WebNotificationButtonView : public TrayPopupTextButtonContainer,
+ public views::ButtonListener {
+ public:
+ explicit WebNotificationButtonView(WebNotificationTray* tray)
+ : tray_(tray),
+ settings_button_(NULL),
+ close_all_button_(NULL) {
+ set_background(views::Background::CreateBackgroundPainter(
+ true,
+ views::Painter::CreateVerticalGradient(
+ kHeaderBackgroundColorLight,
+ kHeaderBackgroundColorDark)));
+ set_border(views::Border::CreateSolidSidedBorder(
+ 2, 0, 0, 0, ash::kBorderDarkColor));
+
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ settings_button_ = new TrayPopupTextButton(
+ this, rb.GetLocalizedString(IDS_ASH_WEB_NOTFICATION_TRAY_SETTINGS));
+ AddTextButton(settings_button_);
+
+ close_all_button_ = new TrayPopupTextButton(
+ this, rb.GetLocalizedString(IDS_ASH_WEB_NOTFICATION_TRAY_CLOSE_ALL));
+ AddTextButton(close_all_button_);
+ }
+
+ virtual ~WebNotificationButtonView() {
+ }
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const views::Event& event) OVERRIDE {
+ if (sender == settings_button_)
+ tray_->ShowSettings("");
+ else if (sender == close_all_button_)
+ tray_->RemoveAllNotifications();
+ }
+
+ private:
+ WebNotificationTray* tray_;
+ TrayPopupTextButton* settings_button_;
+ TrayPopupTextButton* close_all_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationButtonView);
+};
+
+} // namespace internal
+
+using internal::WebNotificationList;
+using internal::WebNotificationView;
+
+class WebNotificationTray::BubbleContentsView : public views::View {
+ public:
+ explicit BubbleContentsView(WebNotificationTray* tray)
+ : tray_(tray) {
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+
+ scroll_content_ = new views::View;
+ scroll_content_->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+ scroller_ = new internal::FixedSizedScrollView;
+ scroller_->SetContentsView(scroll_content_);
+ AddChildView(scroller_);
+
+ button_view_ = new internal::WebNotificationButtonView(tray);
+ AddChildView(button_view_);
+}
+
+ void Update(const WebNotificationList::Notifications& notifications) {
+ scroll_content_->RemoveAllChildViews(true);
+ for (WebNotificationList::Notifications::const_iterator iter =
+ notifications.begin(); iter != notifications.end(); ++iter) {
+ WebNotificationView* view = new WebNotificationView(tray_, *iter);
+ scroll_content_->AddChildView(view);
+ }
+ SizeScrollContent();
+ scroller_->Layout();
+ Layout();
+ PreferredSizeChanged();
+ SchedulePaint();
+ }
+
+ private:
+ void SizeScrollContent() {
+ gfx::Size scroll_size = scroll_content_->GetPreferredSize();
+ int button_height = button_view_->GetPreferredSize().height();
+ int scroll_height = std::min(
+ std::max(scroll_size.height(),
+ kWebNotificationBubbleMinHeight - button_height),
+ kWebNotificationBubbleMaxHeight - button_height);
+ scroll_size.set_height(scroll_height);
+ scroller_->set_fixed_size(scroll_size);
+ }
+
+ WebNotificationTray* tray_;
+ internal::FixedSizedScrollView* scroller_;
+ views::View* scroll_content_;
+ internal::WebNotificationButtonView* button_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
+};
+
+class WebNotificationTray::Bubble : public internal::TrayBubbleView::Host,
+ public views::Widget::Observer {
+ public:
+ explicit Bubble(WebNotificationTray* tray)
+ : tray_(tray),
+ bubble_view_(NULL),
+ bubble_widget_(NULL),
+ contents_view_(NULL) {
+ views::View* anchor = tray->tray_container();
+ views::BubbleBorder::ArrowLocation arrow_location;
+ int arrow_offset = 0;
+ if (tray_->shelf_alignment() == SHELF_ALIGNMENT_BOTTOM) {
+ arrow_location = views::BubbleBorder::BOTTOM_RIGHT;
+ arrow_offset = anchor->GetContentsBounds().width() / 2;
+ } else if (tray_->shelf_alignment() == SHELF_ALIGNMENT_LEFT) {
+ arrow_location = views::BubbleBorder::LEFT_BOTTOM;
+ } else {
+ arrow_location = views::BubbleBorder::RIGHT_BOTTOM;
+ }
+ bubble_view_ = new internal::TrayBubbleView(
+ anchor, arrow_location, this, false, kWebNotificationWidth);
+ bubble_view_->SetMaxHeight(kWebNotificationBubbleMaxHeight);
+
+ bubble_widget_ = views::BubbleDelegateView::CreateBubble(bubble_view_);
+
+ bubble_view_->SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
+ bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
+ bubble_view_->SetBubbleBorder(arrow_offset);
+
+ bubble_widget_->AddObserver(this);
+
+ contents_view_ = new BubbleContentsView(tray);
+ bubble_view_->AddChildView(contents_view_);
+
+ Update();
+ bubble_view_->Show();
+ }
+
+ virtual ~Bubble() {
+ if (bubble_view_)
+ bubble_view_->reset_host();
+ if (bubble_widget_) {
+ bubble_widget_->RemoveObserver(this);
+ bubble_widget_->Close();
+ }
+ }
+
+ void Update() {
+ contents_view_->Update(tray_->notification_list()->notifications());
+ bubble_view_->Layout();
+ bubble_view_->SchedulePaint();
+ }
+
+ views::Widget* bubble_widget() const { return bubble_widget_; }
+
+ // Overridden from TrayBubbleView::Host.
+ virtual void BubbleViewDestroyed() OVERRIDE {
+ bubble_view_ = NULL;
+ contents_view_ = NULL;
+ }
+
+ virtual gfx::Rect GetAnchorRect() const OVERRIDE {
+ gfx::Rect anchor_rect = tray_->tray_container()->GetScreenBounds();
+ return anchor_rect;
+ }
+
+ virtual void OnMouseEnteredView() OVERRIDE {
+ }
+
+ virtual void OnMouseExitedView() OVERRIDE {
+ }
+
+ // Overridden from views::Widget::Observer.
+ virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE {
+ CHECK_EQ(bubble_widget_, widget);
+ bubble_widget_ = NULL;
+ tray_->HideBubble(); // Will destroy |this|.
+ }
+
+ private:
+ WebNotificationTray* tray_;
+ internal::TrayBubbleView* bubble_view_;
+ views::Widget* bubble_widget_;
+ BubbleContentsView* contents_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(Bubble);
+};
+
+WebNotificationTray::WebNotificationTray(
+ internal::StatusAreaWidget* status_area_widget)
+ : status_area_widget_(status_area_widget),
+ notification_list_(new WebNotificationList()),
+ tray_container_(NULL),
+ icon_(NULL),
+ delegate_(NULL) {
+ tray_container_ = new views::View;
+ tray_container_->set_border(views::Border::CreateEmptyBorder(
+ kTrayBorder, kTrayBorder, kTrayBorder, kTrayBorder));
+ SetShelfAlignment(shelf_alignment());
+
+ icon_ = new views::ImageView;
+ tray_container_->AddChildView(icon_);
+ UpdateIcon(); // Hides the tray initially.
+
+ SetContents(tray_container_);
+
+ Shell::GetInstance()->AddEnvEventFilter(this);
+}
+
+WebNotificationTray::~WebNotificationTray() {
+ Shell::GetInstance()->RemoveEnvEventFilter(this);
+}
+
+void WebNotificationTray::SetDelegate(Delegate* delegate) {
+ DCHECK(!delegate_);
+ delegate_ = delegate;
+}
+
+void WebNotificationTray::AddNotification(const std::string& id,
+ const string16& title,
+ const string16& message,
+ const string16& display_source,
+ const std::string& extension_id) {
+ notification_list_->AddNotification(
+ id, title, message, display_source, extension_id);
+ UpdateIcon();
+ if (bubble()) {
+ bubble_->Update();
+ } else {
+ status_area_widget_->ShowWebNotificationBubble(
+ internal::StatusAreaWidget::NON_USER_ACTION);
+ }
+}
+
+void WebNotificationTray::UpdateNotification(const std::string& id,
+ const string16& title,
+ const string16& message) {
+ notification_list_->UpdateNotificationMessage(id, title, message);
+ if (bubble())
+ bubble_->Update();
+}
+
+void WebNotificationTray::RemoveNotification(const std::string& id) {
+ if (!notification_list_->RemoveNotification(id))
+ return;
+ if (delegate_)
+ delegate_->NotificationRemoved(id);
+ UpdateBubbleAndIcon();
+}
+
+void WebNotificationTray::RemoveAllNotifications() {
+ const WebNotificationList::Notifications& notifications =
+ notification_list_->notifications();
+ if (delegate_) {
+ for (WebNotificationList::Notifications::const_iterator loopiter =
+ notifications.begin();
+ loopiter != notifications.end(); ) {
+ WebNotificationList::Notifications::const_iterator curiter = loopiter++;
+ std::string notification_id = curiter->id;
+ // May call RemoveNotification and erase curiter.
+ delegate_->NotificationRemoved(notification_id);
+ }
+ }
+ notification_list_->RemoveAllNotifications();
+ UpdateBubbleAndIcon();
+}
+
+void WebNotificationTray::SetNotificationImage(const std::string& id,
+ const gfx::ImageSkia& image) {
+ if (!notification_list_->SetNotificationImage(id, image))
+ return;
+ if (bubble())
+ bubble_->Update();
+}
+
+void WebNotificationTray::DisableByExtension(const std::string& id) {
+ // When we disable notifications, we remove any existing matching
+ // notifications to avoid adding complicated UI to re-enable the source.
+ notification_list_->RemoveNotificationsByExtension(id);
+ UpdateBubbleAndIcon();
+ if (delegate_)
+ delegate_->DisableExtension(id);
+}
+
+void WebNotificationTray::DisableByUrl(const std::string& id) {
+ // See comment for DisableByExtension.
+ notification_list_->RemoveNotificationsBySource(id);
+ UpdateBubbleAndIcon();
+ if (delegate_)
+ delegate_->DisableNotificationsFromSource(id);
+}
+
+void WebNotificationTray::ShowBubble() {
+ if (bubble())
+ return;
+ bubble_.reset(new Bubble(this));
+}
+
+void WebNotificationTray::HideBubble() {
+ bubble_.reset();
+}
+
+void WebNotificationTray::ShowSettings(const std::string& id) {
+ if (delegate_)
+ delegate_->ShowSettings(id);
+}
+
+void WebNotificationTray::OnClicked(const std::string& id) {
+ if (delegate_)
+ delegate_->OnClicked(id);
+}
+
+bool WebNotificationTray::PreHandleKeyEvent(aura::Window* target,
+ aura::KeyEvent* event) {
+ return false;
+}
+
+bool WebNotificationTray::PreHandleMouseEvent(aura::Window* target,
+ aura::MouseEvent* event) {
+ if (event->type() == ui::ET_MOUSE_PRESSED)
+ return ProcessLocatedEvent(*event);
+ return false;
+}
+
+ui::TouchStatus WebNotificationTray::PreHandleTouchEvent(
+ aura::Window* target,
+ aura::TouchEvent* event) {
+ if (event->type() != ui::ET_TOUCH_PRESSED)
+ return ui::TOUCH_STATUS_UNKNOWN;
+ if (ProcessLocatedEvent(*event))
+ return ui::TOUCH_STATUS_END;
+ return ui::TOUCH_STATUS_UNKNOWN;
+}
+
+ui::GestureStatus WebNotificationTray::PreHandleGestureEvent(
+ aura::Window* target,
+ aura::GestureEvent* event) {
+ return ui::GESTURE_STATUS_UNKNOWN;
+}
+
+void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment) {
+ internal::TrayBackgroundView::SetShelfAlignment(alignment);
+ tray_container_->SetLayoutManager(new views::BoxLayout(
+ alignment == SHELF_ALIGNMENT_BOTTOM ?
+ views::BoxLayout::kHorizontal : views::BoxLayout::kVertical,
+ 0, 0, 0));
+}
+
+bool WebNotificationTray::PerformAction(const views::Event& event) {
+ if (bubble()) {
+ status_area_widget_->HideWebNotificationBubble();
+ } else {
+ status_area_widget_->ShowWebNotificationBubble(
+ internal::StatusAreaWidget::USER_ACTION);
+ }
+ return true;
+}
+
+int WebNotificationTray::GetNotificationCount() const {
+ return notification_list()->notifications().size();
+}
+
+void WebNotificationTray::UpdateIcon() {
+ int count = GetNotificationCount();
+ if (count == 0) {
+ SetVisible(false);
+ } else {
+ icon_->SetImage(gfx::ImageSkia(GetNotificationImage(count)));
+ SetVisible(true);
+ }
+ PreferredSizeChanged();
+}
+
+void WebNotificationTray::UpdateBubbleAndIcon() {
+ UpdateIcon();
+ if (!bubble())
+ return;
+ if (GetNotificationCount() == 0)
+ status_area_widget_->HideWebNotificationBubble();
+ else
+ bubble_->Update();
+}
+
+bool WebNotificationTray::ProcessLocatedEvent(const aura::LocatedEvent& event) {
+ if (!bubble())
+ return false;
+ gfx::Rect bounds =
+ bubble_->bubble_widget()->GetNativeWindow()->GetBoundsInRootWindow();
+ if (bounds.Contains(event.root_location()))
+ return false;
+ status_area_widget_->HideWebNotificationBubble();
+ // If the event occurred in the tray widget, don't process the click.
+ bounds = GetWidget()->GetNativeWindow()->GetBoundsInRootWindow();
+ if (bounds.Contains(event.root_location()))
+ return true;
+ return false;
+}
+
+} // namespace ash
diff --git a/ash/system/web_notification/web_notification_tray.h b/ash/system/web_notification/web_notification_tray.h
new file mode 100644
index 0000000..18219da
--- /dev/null
+++ b/ash/system/web_notification/web_notification_tray.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_
+#define ASH_SYSTEM_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_
+#pragma once
+
+#include "ash/ash_export.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "base/gtest_prod_util.h"
+#include "ui/aura/event_filter.h"
+
+namespace aura {
+class LocatedEvent;
+}
+
+namespace gfx {
+class ImageSkia;
+}
+
+namespace views {
+class ImageView;
+}
+
+namespace ash {
+
+namespace internal {
+class StatusAreaWidget;
+class WebNotificationList;
+}
+
+// Status area tray for showing browser and app notifications. The client
+// (e.g. Chrome) calls [Add|Remove|Update]Notification to create and update
+// notifications in the tray. It can also implement Delegate to receive
+// callbacks when a notification is removed (closed), clicked on, or a menu
+// item is triggered.
+//
+// Note: These are not related to system notifications (i.e NotificationView
+// generated by SystemTrayItem). Visibility of one notification type or other
+// is controlled by StatusAreaWidget.
+
+class ASH_EXPORT WebNotificationTray : public aura::EventFilter,
+ public internal::TrayBackgroundView {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called when the notification associated with |notification_id| is
+ // removed (i.e. closed by the user).
+ virtual void NotificationRemoved(const std::string& notifcation_id) = 0;
+
+ // Request to disable the extension associated with |notification_id|.
+ virtual void DisableExtension(const std::string& notifcation_id) = 0;
+
+ // Request to disable notifications from the source of |notification_id|.
+ virtual void DisableNotificationsFromSource(
+ const std::string& notifcation_id) = 0;
+
+ // Request to show the notification settings (|notification_id| is used
+ // to identify the requesting browser context).
+ virtual void ShowSettings(const std::string& notifcation_id) = 0;
+
+ // Called when the notification body is clicked on.
+ virtual void OnClicked(const std::string& notifcation_id) = 0;
+ };
+
+ explicit WebNotificationTray(internal::StatusAreaWidget* status_area_widget);
+ virtual ~WebNotificationTray();
+
+ // Called once to set the delegate.
+ void SetDelegate(Delegate* delegate);
+
+ // Add a new notification. |id| is a unique identifier, used to update or
+ // remove notifications. |title| and |meesage| describe the notification text.
+ // Use SetNotificationImage to set the icon image. If |extension_id| is
+ // provided then 'Disable extension' will appear in a dropdown menu and the
+ // id will be used to disable notifications from the extension. Otherwise if
+ // |display_source| is provided, a menu item showing the source and allowing
+ // notifications from that source to be disabled will be shown. All actual
+ // disabling is handled by the Delegate.
+ void AddNotification(const std::string& id,
+ const string16& title,
+ const string16& message,
+ const string16& display_source,
+ const std::string& extension_id);
+
+ // Update an existing notification.
+ void UpdateNotification(const std::string& id,
+ const string16& title,
+ const string16& message);
+
+ // Remove an existing notification and notify the delegate.
+ void RemoveNotification(const std::string& id);
+
+ // Remove all notifications and notify the delegate.
+ void RemoveAllNotifications();
+
+ // Set the notification image.
+ void SetNotificationImage(const std::string& id,
+ const gfx::ImageSkia& image);
+
+ // Disable all notifications matching notification |id|.
+ void DisableByExtension(const std::string& id);
+ void DisableByUrl(const std::string& id);
+
+ // Show the notification bubble. Should only be called by StatusAreaWidget.
+ void ShowBubble();
+
+ // Hide the notification bubble. Should only be called by StatusAreaWidget.
+ void HideBubble();
+
+ // Request the Delegate to the settings dialog.
+ void ShowSettings(const std::string& id);
+
+ // Called when a notification is clicked on. Event is passed to the Delegate.
+ void OnClicked(const std::string& id);
+
+ // Overridden from aura::EventFilter.
+ virtual bool PreHandleKeyEvent(aura::Window* target,
+ aura::KeyEvent* event) OVERRIDE;
+ virtual bool PreHandleMouseEvent(aura::Window* target,
+ aura::MouseEvent* event) OVERRIDE;
+ virtual ui::TouchStatus PreHandleTouchEvent(aura::Window* target,
+ aura::TouchEvent* event) OVERRIDE;
+ virtual ui::GestureStatus PreHandleGestureEvent(
+ aura::Window* target,
+ aura::GestureEvent* event) OVERRIDE;
+
+ // Overridden from TrayBackgroundView.
+ virtual void SetShelfAlignment(ShelfAlignment alignment) OVERRIDE;
+
+ // Overridden from internal::ActionableView.
+ virtual bool PerformAction(const views::Event& event) OVERRIDE;
+
+ private:
+ class Bubble;
+ class BubbleContentsView;
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, WebNotifications);
+
+ int GetNotificationCount() const;
+ void UpdateIcon();
+ void UpdateBubbleAndIcon();
+ bool ProcessLocatedEvent(const aura::LocatedEvent& event);
+
+ const internal::WebNotificationList* notification_list() const {
+ return notification_list_.get();
+ }
+ views::View* tray_container() const { return tray_container_; }
+ Bubble* bubble() const { return bubble_.get(); }
+
+ internal::StatusAreaWidget* status_area_widget_; // Unowned parent.
+ scoped_ptr<internal::WebNotificationList> notification_list_;
+ scoped_ptr<Bubble> bubble_;
+ views::View* tray_container_;
+ views::ImageView* icon_;
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationTray);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_
diff --git a/ash/system/web_notification/web_notification_tray_unittest.cc b/ash/system/web_notification/web_notification_tray_unittest.cc
new file mode 100644
index 0000000..a153371
--- /dev/null
+++ b/ash/system/web_notification/web_notification_tray_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/web_notification/web_notification_tray.h"
+
+#include <vector>
+
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/test/ash_test_base.h"
+#include "base/utf_string_conversions.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+WebNotificationTray* CreateWebNotificationTray() {
+ internal::StatusAreaWidget* widget = new internal::StatusAreaWidget;
+ widget->CreateTrayViews(NULL);
+ widget->Show();
+ return widget->web_notification_tray();
+}
+
+class TestDelegate : public WebNotificationTray::Delegate {
+ public:
+ TestDelegate() {}
+ virtual ~TestDelegate() {}
+
+ // WebNotificationTray::Delegate overrides.
+ virtual void NotificationRemoved(const std::string& notifcation_id) {
+ notification_ids_.erase(notifcation_id);
+ }
+
+ virtual void DisableExtension(const std::string& notifcation_id) {
+ }
+
+ virtual void DisableNotificationsFromSource(
+ const std::string& notifcation_id) {
+ }
+
+ virtual void ShowSettings(const std::string& notifcation_id) {
+ }
+
+ virtual void OnClicked(const std::string& notifcation_id) {
+ }
+
+ void AddNotification(WebNotificationTray* tray, const std::string& id) {
+ notification_ids_.insert(id);
+ tray->AddNotification(id,
+ ASCIIToUTF16("Test Web Notification"),
+ ASCIIToUTF16("Notification message body."),
+ ASCIIToUTF16("www.test.org"),
+ "" /* extension id */);
+ }
+
+ void RemoveNotification(WebNotificationTray* tray, const std::string& id) {
+ tray->RemoveNotification(id);
+ }
+
+ bool HasNotificationId(const std::string& id) {
+ return notification_ids_.find(id) != notification_ids_.end();
+ }
+
+ private:
+ std::set<std::string> notification_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDelegate);
+};
+
+} // namespace
+
+typedef test::AshTestBase WebNotificationTrayTest;
+
+TEST_F(WebNotificationTrayTest, WebNotifications) {
+ scoped_ptr<WebNotificationTray> tray(CreateWebNotificationTray());
+ scoped_ptr<TestDelegate> delegate(new TestDelegate);
+ tray->SetDelegate(delegate.get());
+
+ ASSERT_TRUE(tray->GetWidget());
+
+ // Adding a notification should show the bubble.
+ delegate->AddNotification(tray.get(), "test_id1");
+ EXPECT_TRUE(tray->bubble() != NULL);
+ EXPECT_EQ(1, tray->GetNotificationCount());
+ delegate->AddNotification(tray.get(), "test_id2");
+ delegate->AddNotification(tray.get(), "test_id2");
+ EXPECT_EQ(2, tray->GetNotificationCount());
+ // Ensure that removing a notification removes it from the tray, and signals
+ // the delegate.
+ EXPECT_TRUE(delegate->HasNotificationId("test_id2"));
+ delegate->RemoveNotification(tray.get(), "test_id2");
+ EXPECT_FALSE(delegate->HasNotificationId("test_id2"));
+ EXPECT_EQ(1, tray->GetNotificationCount());
+
+ // Removing the last notification should hide the bubble.
+ delegate->RemoveNotification(tray.get(), "test_id1");
+ EXPECT_EQ(0, tray->GetNotificationCount());
+ EXPECT_TRUE(tray->bubble() == NULL);
+}
+
+} // namespace ash
diff --git a/ash/wm/shelf_layout_manager.cc b/ash/wm/shelf_layout_manager.cc
index 3f2e857..e6c674a 100644
--- a/ash/wm/shelf_layout_manager.cc
+++ b/ash/wm/shelf_layout_manager.cc
@@ -13,6 +13,7 @@
#include "ash/shell_window_ids.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/system_tray.h"
+#include "ash/system/web_notification/web_notification_tray.h"
#include "ash/wm/workspace/workspace_manager.h"
#include "base/auto_reset.h"
#include "base/i18n/rtl.h"
@@ -196,10 +197,10 @@ bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
alignment_ = alignment;
if (launcher_)
launcher_->SetAlignment(alignment);
- if (Shell::GetInstance()->status_area_widget())
+ StatusAreaWidget* status_area_widget =
+ Shell::GetInstance()->status_area_widget();
+ if (status_area_widget)
Shell::GetInstance()->status_area_widget()->SetShelfAlignment(alignment);
- if (Shell::GetInstance()->system_tray())
- Shell::GetInstance()->system_tray()->SetShelfAlignment(alignment);
LayoutShelf();
return true;
}
@@ -493,10 +494,10 @@ void ShelfLayoutManager::UpdateShelfBackground(
launcher_->SetPaintsBackground(launcher_paints, type);
// SystemTray normally draws a background, but we don't want it to draw a
// background when the launcher does.
- if (Shell::GetInstance()->system_tray()) {
- Shell::GetInstance()->system_tray()->SetPaintsBackground(
- !launcher_paints, type);
- }
+ StatusAreaWidget* status_area_widget =
+ Shell::GetInstance()->status_area_widget();
+ if (status_area_widget)
+ status_area_widget->SetPaintsBackground(!launcher_paints, type);
}
bool ShelfLayoutManager::GetLauncherPaintsBackground() const {