summaryrefslogtreecommitdiffstats
path: root/chrome/browser/notifications
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-07-29 17:14:53 +0100
committerBen Murdoch <benm@google.com>2010-08-04 14:29:45 +0100
commitc407dc5cd9bdc5668497f21b26b09d988ab439de (patch)
tree7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/notifications
parent0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff)
downloadexternal_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/notifications')
-rw-r--r--chrome/browser/notifications/balloon.cc64
-rw-r--r--chrome/browser/notifications/balloon.h134
-rw-r--r--chrome/browser/notifications/balloon_collection.cc321
-rw-r--r--chrome/browser/notifications/balloon_collection.h75
-rw-r--r--chrome/browser/notifications/balloon_collection_impl.h181
-rw-r--r--chrome/browser/notifications/balloon_collection_linux.cc68
-rw-r--r--chrome/browser/notifications/balloon_collection_mac.mm39
-rw-r--r--chrome/browser/notifications/balloon_collection_win.cc63
-rw-r--r--chrome/browser/notifications/balloon_host.cc171
-rw-r--r--chrome/browser/notifications/balloon_host.h150
-rw-r--r--chrome/browser/notifications/desktop_notification_service.cc582
-rw-r--r--chrome/browser/notifications/desktop_notification_service.h136
-rw-r--r--chrome/browser/notifications/desktop_notification_service_unittest.cc246
-rw-r--r--chrome/browser/notifications/desktop_notifications_unittest.cc327
-rw-r--r--chrome/browser/notifications/desktop_notifications_unittest.h119
-rw-r--r--chrome/browser/notifications/notification.h88
-rw-r--r--chrome/browser/notifications/notification_delegate.h34
-rw-r--r--chrome/browser/notifications/notification_exceptions_table_model.cc101
-rw-r--r--chrome/browser/notifications/notification_exceptions_table_model.h51
-rw-r--r--chrome/browser/notifications/notification_exceptions_table_model_unittest.cc125
-rw-r--r--chrome/browser/notifications/notification_object_proxy.cc77
-rw-r--r--chrome/browser/notifications/notification_object_proxy.h53
-rw-r--r--chrome/browser/notifications/notification_options_menu_model.cc96
-rw-r--r--chrome/browser/notifications/notification_options_menu_model.h30
-rw-r--r--chrome/browser/notifications/notification_test_util.h54
-rw-r--r--chrome/browser/notifications/notification_ui_manager.cc132
-rw-r--r--chrome/browser/notifications/notification_ui_manager.h78
-rw-r--r--chrome/browser/notifications/notifications_interactive_uitest.cc69
-rw-r--r--chrome/browser/notifications/notifications_prefs_cache.cc100
-rw-r--r--chrome/browser/notifications/notifications_prefs_cache.h81
-rw-r--r--chrome/browser/notifications/notifications_prefs_cache_unittest.cc63
31 files changed, 3908 insertions, 0 deletions
diff --git a/chrome/browser/notifications/balloon.cc b/chrome/browser/notifications/balloon.cc
new file mode 100644
index 0000000..969bde1
--- /dev/null
+++ b/chrome/browser/notifications/balloon.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2009 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 "chrome/browser/notifications/balloon.h"
+
+#include "base/logging.h"
+#include "chrome/browser/notifications/balloon_collection.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "gfx/rect.h"
+
+Balloon::Balloon(const Notification& notification, Profile* profile,
+ BalloonCollection* collection)
+ : profile_(profile),
+ notification_(new Notification(notification)),
+ collection_(collection) {
+}
+
+Balloon::~Balloon() {
+}
+
+void Balloon::SetPosition(const gfx::Point& upper_left, bool reposition) {
+ position_ = upper_left;
+ if (reposition && balloon_view_.get())
+ balloon_view_->RepositionToBalloon();
+}
+
+void Balloon::SetContentPreferredSize(const gfx::Size& size) {
+ collection_->ResizeBalloon(this, size);
+}
+
+void Balloon::set_view(BalloonView* balloon_view) {
+ balloon_view_.reset(balloon_view);
+}
+
+void Balloon::Show() {
+ notification_->Display();
+ if (balloon_view_.get()) {
+ balloon_view_->Show(this);
+ balloon_view_->RepositionToBalloon();
+ }
+}
+
+void Balloon::Update(const Notification& notification) {
+ notification_->Close(false);
+ notification_.reset(new Notification(notification));
+ notification_->Display();
+ if (balloon_view_.get()) {
+ balloon_view_->Update();
+ }
+}
+
+void Balloon::OnClose(bool by_user) {
+ notification_->Close(by_user);
+ collection_->OnBalloonClosed(this);
+}
+
+void Balloon::CloseByScript() {
+ // A user-initiated close begins with the view and then closes this object;
+ // we simulate that with a script-initiated close but pass |by_user|=false.
+ DCHECK(balloon_view_.get());
+ balloon_view_->Close(false);
+}
diff --git a/chrome/browser/notifications/balloon.h b/chrome/browser/notifications/balloon.h
new file mode 100644
index 0000000..7f3b604
--- /dev/null
+++ b/chrome/browser/notifications/balloon.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2009 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.
+
+// Handles the visible notification (or balloons).
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_BALLOON_H_
+#define CHROME_BROWSER_NOTIFICATIONS_BALLOON_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "gfx/point.h"
+#include "gfx/rect.h"
+#include "gfx/size.h"
+
+class Balloon;
+class BalloonCollection;
+class BalloonHost;
+class Notification;
+class Profile;
+class SiteInstance;
+
+// Interface for a view that displays a balloon.
+class BalloonView {
+ public:
+ virtual ~BalloonView() { }
+
+ // Show the view on the screen.
+ virtual void Show(Balloon* balloon) = 0;
+
+ // Notify that the content of notification has chagned.
+ virtual void Update() = 0;
+
+ // Reposition the view to match the position of its balloon.
+ virtual void RepositionToBalloon() = 0;
+
+ // Close the view.
+ virtual void Close(bool by_user) = 0;
+
+ // The total size of the view.
+ virtual gfx::Size GetSize() const = 0;
+
+ // The host for the view's contents.
+ virtual BalloonHost* GetHost() const = 0;
+};
+
+// Represents a Notification on the screen.
+class Balloon {
+ public:
+ Balloon(const Notification& notification,
+ Profile* profile,
+ BalloonCollection* collection);
+ virtual ~Balloon();
+
+ const Notification& notification() const { return *notification_.get(); }
+ Profile* profile() const { return profile_; }
+
+ gfx::Point GetPosition() const {
+ return position_.Add(offset_);
+ }
+ void SetPosition(const gfx::Point& upper_left, bool reposition);
+
+ const gfx::Point& offset() { return offset_;}
+ void set_offset(const gfx::Point& offset) { offset_ = offset; }
+ void add_offset(const gfx::Point& offset) { offset_ = offset_.Add(offset); }
+
+ const gfx::Size& content_size() const { return content_size_; }
+ void set_content_size(const gfx::Size& size) { content_size_ = size; }
+
+ const gfx::Size& min_scrollbar_size() const { return min_scrollbar_size_; }
+ void set_min_scrollbar_size(const gfx::Size& size) {
+ min_scrollbar_size_ = size;
+ }
+
+ // Request a new content size for this balloon. This will get passed
+ // to the balloon collection for checking against available space and
+ // min/max restrictions.
+ void SetContentPreferredSize(const gfx::Size& size);
+
+ // Provides a view for this balloon. Ownership transfers
+ // to this object.
+ void set_view(BalloonView* balloon_view);
+
+ // Returns the balloon view associated with the balloon.
+ BalloonView* view() const {
+ return balloon_view_.get();
+ }
+
+ // Returns the viewing size for the balloon (content + frame).
+ gfx::Size GetViewSize() const { return balloon_view_->GetSize(); }
+
+ // Shows the balloon.
+ virtual void Show();
+
+ // Notify that the content of notification has changed.
+ virtual void Update(const Notification& notification);
+
+ // Called when the balloon is closed, either by user (through the UI)
+ // or by a script.
+ virtual void OnClose(bool by_user);
+
+ // Called by script to cause the balloon to close.
+ virtual void CloseByScript();
+
+ private:
+ // Non-owned pointer to the profile.
+ Profile* profile_;
+
+ // The notification being shown in this balloon.
+ scoped_ptr<Notification> notification_;
+
+ // The collection that this balloon belongs to. Non-owned pointer.
+ BalloonCollection* collection_;
+
+ // The actual UI element for the balloon.
+ scoped_ptr<BalloonView> balloon_view_;
+
+ // Position and size of the balloon on the screen.
+ gfx::Point position_;
+ gfx::Size content_size_;
+
+ // Temporary offset for balloons that need to be positioned in a non-standard
+ // position for keeping the close buttons under the mouse cursor.
+ gfx::Point offset_;
+
+ // Smallest size for this balloon where scrollbars will be shown.
+ gfx::Size min_scrollbar_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(Balloon);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_BALLOON_H_
diff --git a/chrome/browser/notifications/balloon_collection.cc b/chrome/browser/notifications/balloon_collection.cc
new file mode 100644
index 0000000..3b66a07
--- /dev/null
+++ b/chrome/browser/notifications/balloon_collection.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/balloon_collection_impl.h"
+
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/balloon_host.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/window_sizer.h"
+#include "gfx/rect.h"
+#include "gfx/size.h"
+
+namespace {
+
+// Portion of the screen allotted for notifications. When notification balloons
+// extend over this, no new notifications are shown until some are closed.
+const double kPercentBalloonFillFactor = 0.7;
+
+// Allow at least this number of balloons on the screen.
+const int kMinAllowedBalloonCount = 2;
+
+// Delay from the mouse leaving the balloon collection before
+// there is a relayout, in milliseconds.
+const int kRepositionDelay = 300;
+
+} // namespace
+
+// static
+// Note that on MacOS, since the coordinate system is inverted vertically from
+// the others, this actually produces notifications coming from the TOP right,
+// which is what is desired.
+BalloonCollectionImpl::Layout::Placement
+ BalloonCollectionImpl::Layout::placement_ =
+ Layout::VERTICALLY_FROM_BOTTOM_RIGHT;
+
+BalloonCollectionImpl::BalloonCollectionImpl()
+#if USE_OFFSETS
+ : ALLOW_THIS_IN_INITIALIZER_LIST(reposition_factory_(this)),
+ added_as_message_loop_observer_(false)
+#endif
+{
+}
+
+BalloonCollectionImpl::~BalloonCollectionImpl() {
+ STLDeleteElements(&balloons_);
+}
+
+void BalloonCollectionImpl::Add(const Notification& notification,
+ Profile* profile) {
+ Balloon* new_balloon = MakeBalloon(notification, profile);
+ // The +1 on width is necessary because width is fixed on notifications,
+ // so since we always have the max size, we would always hit the scrollbar
+ // condition. We are only interested in comparing height to maximum.
+ new_balloon->set_min_scrollbar_size(gfx::Size(1 + layout_.max_balloon_width(),
+ layout_.max_balloon_height()));
+ new_balloon->SetPosition(layout_.OffScreenLocation(), false);
+ new_balloon->Show();
+#if USE_OFFSETS
+ if (balloons_.size() > 0)
+ new_balloon->set_offset(balloons_[balloons_.size() - 1]->offset());
+#endif
+
+ balloons_.push_back(new_balloon);
+ PositionBalloons(false);
+
+ // There may be no listener in a unit test.
+ if (space_change_listener_)
+ space_change_listener_->OnBalloonSpaceChanged();
+}
+
+bool BalloonCollectionImpl::Remove(const Notification& notification) {
+ Balloons::iterator iter;
+ for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) {
+ if (notification.IsSame((*iter)->notification())) {
+ // Balloon.CloseByScript() will cause OnBalloonClosed() to be called on
+ // this object, which will remove it from the collection and free it.
+ (*iter)->CloseByScript();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool BalloonCollectionImpl::HasSpace() const {
+ if (count() < kMinAllowedBalloonCount)
+ return true;
+
+ int max_balloon_size = 0;
+ int total_size = 0;
+ layout_.GetMaxLinearSize(&max_balloon_size, &total_size);
+
+ int current_max_size = max_balloon_size * count();
+ int max_allowed_size = static_cast<int>(total_size *
+ kPercentBalloonFillFactor);
+ return current_max_size < max_allowed_size - max_balloon_size;
+}
+
+void BalloonCollectionImpl::ResizeBalloon(Balloon* balloon,
+ const gfx::Size& size) {
+ balloon->set_content_size(Layout::ConstrainToSizeLimits(size));
+ PositionBalloons(true);
+}
+
+void BalloonCollectionImpl::DisplayChanged() {
+ layout_.RefreshSystemMetrics();
+ PositionBalloons(true);
+}
+
+void BalloonCollectionImpl::OnBalloonClosed(Balloon* source) {
+ // We want to free the balloon when finished.
+ scoped_ptr<Balloon> closed(source);
+ Balloons::iterator it = balloons_.begin();
+
+#if USE_OFFSETS
+ gfx::Point offset;
+ bool apply_offset = false;
+ while (it != balloons_.end()) {
+ if (*it == source) {
+ it = balloons_.erase(it);
+ if (it != balloons_.end()) {
+ apply_offset = true;
+ offset.set_y((source)->offset().y() - (*it)->offset().y() +
+ (*it)->content_size().height() - source->content_size().height());
+ }
+ } else {
+ if (apply_offset)
+ (*it)->add_offset(offset);
+ ++it;
+ }
+ }
+ // Start listening for UI events so we cancel the offset when the mouse
+ // leaves the balloon area.
+ if (apply_offset)
+ AddMessageLoopObserver();
+#else
+ for (; it != balloons_.end(); ++it) {
+ if (*it == source) {
+ balloons_.erase(it);
+ break;
+ }
+ }
+#endif
+
+ PositionBalloons(true);
+
+ // There may be no listener in a unit test.
+ if (space_change_listener_)
+ space_change_listener_->OnBalloonSpaceChanged();
+}
+
+void BalloonCollectionImpl::PositionBalloons(bool reposition) {
+ layout_.RefreshSystemMetrics();
+ gfx::Point origin = layout_.GetLayoutOrigin();
+ for (Balloons::iterator it = balloons_.begin(); it != balloons_.end(); ++it) {
+ gfx::Point upper_left = layout_.NextPosition((*it)->GetViewSize(), &origin);
+ (*it)->SetPosition(upper_left, reposition);
+ }
+}
+
+#if USE_OFFSETS
+void BalloonCollectionImpl::AddMessageLoopObserver() {
+ if (!added_as_message_loop_observer_) {
+ MessageLoopForUI::current()->AddObserver(this);
+ added_as_message_loop_observer_ = true;
+ }
+}
+
+void BalloonCollectionImpl::RemoveMessageLoopObserver() {
+ if (added_as_message_loop_observer_) {
+ MessageLoopForUI::current()->RemoveObserver(this);
+ added_as_message_loop_observer_ = false;
+ }
+}
+
+void BalloonCollectionImpl::CancelOffsets() {
+ reposition_factory_.RevokeAll();
+
+ // Unhook from listening to all UI events.
+ RemoveMessageLoopObserver();
+
+ for (Balloons::iterator it = balloons_.begin(); it != balloons_.end(); ++it)
+ (*it)->set_offset(gfx::Point(0, 0));
+
+ PositionBalloons(true);
+}
+
+void BalloonCollectionImpl::HandleMouseMoveEvent() {
+ if (!IsCursorInBalloonCollection()) {
+ // Mouse has left the region. Schedule a reposition after
+ // a short delay.
+ if (reposition_factory_.empty()) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ reposition_factory_.NewRunnableMethod(
+ &BalloonCollectionImpl::CancelOffsets),
+ kRepositionDelay);
+ }
+ } else {
+ // Mouse moved back into the region. Cancel the reposition.
+ reposition_factory_.RevokeAll();
+ }
+}
+#endif
+
+BalloonCollectionImpl::Layout::Layout() {
+ RefreshSystemMetrics();
+}
+
+void BalloonCollectionImpl::Layout::GetMaxLinearSize(int* max_balloon_size,
+ int* total_size) const {
+ DCHECK(max_balloon_size && total_size);
+
+ switch (placement_) {
+ case VERTICALLY_FROM_TOP_RIGHT:
+ case VERTICALLY_FROM_BOTTOM_RIGHT:
+ *total_size = work_area_.height();
+ *max_balloon_size = max_balloon_height();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+gfx::Point BalloonCollectionImpl::Layout::GetLayoutOrigin() const {
+ int x = 0;
+ int y = 0;
+ switch (placement_) {
+ case VERTICALLY_FROM_TOP_RIGHT:
+ x = work_area_.right() - HorizontalEdgeMargin();
+ y = work_area_.y() + VerticalEdgeMargin();
+ break;
+ case VERTICALLY_FROM_BOTTOM_RIGHT:
+ x = work_area_.right() - HorizontalEdgeMargin();
+ y = work_area_.bottom() - VerticalEdgeMargin();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return gfx::Point(x, y);
+}
+
+gfx::Point BalloonCollectionImpl::Layout::NextPosition(
+ const gfx::Size& balloon_size,
+ gfx::Point* position_iterator) const {
+ DCHECK(position_iterator);
+
+ int x = 0;
+ int y = 0;
+ switch (placement_) {
+ case VERTICALLY_FROM_TOP_RIGHT:
+ x = position_iterator->x() - balloon_size.width();
+ y = position_iterator->y();
+ position_iterator->set_y(position_iterator->y() + balloon_size.height() +
+ InterBalloonMargin());
+ break;
+ case VERTICALLY_FROM_BOTTOM_RIGHT:
+ position_iterator->set_y(position_iterator->y() - balloon_size.height() -
+ InterBalloonMargin());
+ x = position_iterator->x() - balloon_size.width();
+ y = position_iterator->y();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return gfx::Point(x, y);
+}
+
+gfx::Point BalloonCollectionImpl::Layout::OffScreenLocation() const {
+ int x = 0;
+ int y = 0;
+ switch (placement_) {
+ case VERTICALLY_FROM_TOP_RIGHT:
+ x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin();
+ y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin();
+ break;
+ case VERTICALLY_FROM_BOTTOM_RIGHT:
+ x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin();
+ y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return gfx::Point(x, y);
+}
+
+// static
+gfx::Size BalloonCollectionImpl::Layout::ConstrainToSizeLimits(
+ const gfx::Size& size) {
+ // restrict to the min & max sizes
+ return gfx::Size(
+ std::max(min_balloon_width(),
+ std::min(max_balloon_width(), size.width())),
+ std::max(min_balloon_height(),
+ std::min(max_balloon_height(), size.height())));
+}
+
+bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() {
+ bool changed = false;
+
+#if defined(OS_MACOSX)
+ gfx::Rect new_work_area = GetMacWorkArea();
+#else
+ scoped_ptr<WindowSizer::MonitorInfoProvider> info_provider(
+ WindowSizer::CreateDefaultMonitorInfoProvider());
+ gfx::Rect new_work_area = info_provider->GetPrimaryMonitorWorkArea();
+#endif
+ if (!work_area_.Equals(new_work_area)) {
+ work_area_.SetRect(new_work_area.x(), new_work_area.y(),
+ new_work_area.width(), new_work_area.height());
+ changed = true;
+ }
+
+ return changed;
+}
diff --git a/chrome/browser/notifications/balloon_collection.h b/chrome/browser/notifications/balloon_collection.h
new file mode 100644
index 0000000..2d868c4
--- /dev/null
+++ b/chrome/browser/notifications/balloon_collection.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2010 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.
+
+// Handles the visible notification (or balloons).
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_BALLOON_COLLECTION_H_
+#define CHROME_BROWSER_NOTIFICATIONS_BALLOON_COLLECTION_H_
+
+#include <deque>
+
+class Balloon;
+class Notification;
+class Profile;
+
+namespace gfx {
+class Size;
+} // namespace gfx
+
+class BalloonCollection {
+ public:
+ class BalloonSpaceChangeListener {
+ public:
+ virtual ~BalloonSpaceChangeListener() {}
+
+ // Called when there is more or less space for balloons due to
+ // monitor size changes or balloons disappearing.
+ virtual void OnBalloonSpaceChanged() = 0;
+ };
+
+ static BalloonCollection* Create();
+
+ BalloonCollection()
+ : space_change_listener_(NULL) {
+ }
+
+ virtual ~BalloonCollection() {}
+
+ // Adds a new balloon for the specified notification.
+ virtual void Add(const Notification& notification,
+ Profile* profile) = 0;
+
+ // Removes a balloon from the collection if present. Returns
+ // true if anything was removed.
+ virtual bool Remove(const Notification& notification) = 0;
+
+ // Is there room to add another notification?
+ virtual bool HasSpace() const = 0;
+
+ // Request the resizing of a balloon.
+ virtual void ResizeBalloon(Balloon* balloon, const gfx::Size& size) = 0;
+
+ // Update for new screen dimensions.
+ virtual void DisplayChanged() = 0;
+
+ // Inform the collection that a balloon was closed.
+ virtual void OnBalloonClosed(Balloon* source) = 0;
+
+ // Get const collection of the active balloons.
+ typedef std::deque<Balloon*> Balloons;
+ virtual const Balloons& GetActiveBalloons() = 0;
+
+ BalloonSpaceChangeListener* space_change_listener() {
+ return space_change_listener_;
+ }
+ void set_space_change_listener(BalloonSpaceChangeListener* listener) {
+ space_change_listener_ = listener;
+ }
+
+ protected:
+ // Non-owned pointer to an object listening for space changes.
+ BalloonSpaceChangeListener* space_change_listener_;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_BALLOON_COLLECTION_H_
diff --git a/chrome/browser/notifications/balloon_collection_impl.h b/chrome/browser/notifications/balloon_collection_impl.h
new file mode 100644
index 0000000..cb06230
--- /dev/null
+++ b/chrome/browser/notifications/balloon_collection_impl.h
@@ -0,0 +1,181 @@
+// Copyright (c) 2010 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.
+
+// Handles the visible notification (or balloons).
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_BALLOON_COLLECTION_IMPL_H_
+#define CHROME_BROWSER_NOTIFICATIONS_BALLOON_COLLECTION_IMPL_H_
+
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "chrome/browser/notifications/balloon_collection.h"
+#include "gfx/point.h"
+#include "gfx/rect.h"
+
+// Mac balloons grow from the top down and have close buttons on top, so
+// offsetting is not necessary for easy multiple-closing. Other platforms grow
+// from the bottom up and have close buttons on top, so it is necessary.
+#if defined(OS_MACOSX)
+#define USE_OFFSETS 0
+#else
+#define USE_OFFSETS 1
+#endif
+
+// A balloon collection represents a set of notification balloons being
+// shown on the screen. It positions new notifications according to
+// a layout, and monitors for balloons being closed, which it reports
+// up to its parent, the notification UI manager.
+class BalloonCollectionImpl : public BalloonCollection
+#if USE_OFFSETS
+ , public MessageLoopForUI::Observer
+#endif
+{
+ public:
+ BalloonCollectionImpl();
+ virtual ~BalloonCollectionImpl();
+
+ // BalloonCollection interface.
+ virtual void Add(const Notification& notification,
+ Profile* profile);
+ virtual bool Remove(const Notification& notification);
+ virtual bool HasSpace() const;
+ virtual void ResizeBalloon(Balloon* balloon, const gfx::Size& size);
+ virtual void DisplayChanged();
+ virtual void OnBalloonClosed(Balloon* source);
+ virtual const Balloons& GetActiveBalloons() {
+ return balloons_;
+ }
+
+ // MessageLoopForUI::Observer interface.
+#if defined(OS_WIN)
+ virtual void WillProcessMessage(const MSG& event) {}
+ virtual void DidProcessMessage(const MSG& event);
+#endif
+#if defined(OS_LINUX)
+ virtual void WillProcessEvent(GdkEvent* event) {}
+ virtual void DidProcessEvent(GdkEvent* event);
+#endif
+
+ protected:
+ // Calculates layout values for the balloons including
+ // the scaling, the max/min sizes, and the upper left corner of each.
+ class Layout {
+ public:
+ Layout();
+
+ // Refresh the work area and balloon placement.
+ void OnDisplaySettingsChanged();
+
+ // TODO(johnnyg): Scale the size to account for the system font factor.
+ static int min_balloon_width() { return kBalloonMinWidth; }
+ static int max_balloon_width() { return kBalloonMaxWidth; }
+ static int min_balloon_height() { return kBalloonMinHeight; }
+ static int max_balloon_height() { return kBalloonMaxHeight; }
+
+ // Utility function constrains the input rectangle to the min and max sizes.
+ static gfx::Size ConstrainToSizeLimits(const gfx::Size& rect);
+
+ // Returns both the total space available and the maximum
+ // allowed per balloon.
+ //
+ // The size may be a height or length depending on the way that
+ // balloons are laid out.
+ void GetMaxLinearSize(int* max_balloon_size, int* total_size) const;
+
+ // Refresh the cached values for work area and drawing metrics.
+ // The application should call this method to re-acquire metrics after
+ // any resolution or settings change.
+ // Returns true if and only if a metric changed.
+ bool RefreshSystemMetrics();
+
+ // Returns the origin for the sequence of balloons depending on layout.
+ // Should not be used to place a balloon -- only to call NextPosition().
+ gfx::Point GetLayoutOrigin() const;
+
+ // Compute the position for the next balloon.
+ // Start with *position_iterator = GetLayoutOrigin() and call repeatedly
+ // to get a sequence of positions. Return value is the upper-left coordinate
+ // for each next balloon.
+ gfx::Point NextPosition(const gfx::Size& balloon_size,
+ gfx::Point* position_iterator) const;
+
+ // Return a offscreen location which is offscreen for this layout,
+ // to be used as the initial position for an animation into view.
+ gfx::Point OffScreenLocation() const;
+
+ private:
+ enum Placement {
+ VERTICALLY_FROM_TOP_RIGHT,
+ VERTICALLY_FROM_BOTTOM_RIGHT
+ };
+
+ // Layout parameters
+ int VerticalEdgeMargin() const;
+ int HorizontalEdgeMargin() const;
+ int InterBalloonMargin() const;
+
+ // Minimum and maximum size of balloon content.
+ static const int kBalloonMinWidth = 300;
+ static const int kBalloonMaxWidth = 300;
+ static const int kBalloonMinHeight = 24;
+ static const int kBalloonMaxHeight = 120;
+
+ static Placement placement_;
+ gfx::Rect work_area_;
+ DISALLOW_COPY_AND_ASSIGN(Layout);
+ };
+
+ // Creates a new balloon. Overridable by unit tests. The caller is
+ // responsible for freeing the pointer returned.
+ virtual Balloon* MakeBalloon(const Notification& notification,
+ Profile* profile);
+
+ private:
+ // The number of balloons being displayed.
+ int count() const { return balloons_.size(); }
+
+ // Adjusts the positions of the balloons (e.g., when one is closed).
+ void PositionBalloons(bool is_reposition);
+
+#if defined(OS_MACOSX)
+ // Get the work area on Mac OS, without inverting the coordinates.
+ static gfx::Rect GetMacWorkArea();
+#endif
+
+#if USE_OFFSETS
+ // Start and stop observing all UI events.
+ void AddMessageLoopObserver();
+ void RemoveMessageLoopObserver();
+
+ // Cancel all offset and reposition the balloons normally.
+ void CancelOffsets();
+
+ // Handles a mouse motion while the balloons are temporarily offset.
+ void HandleMouseMoveEvent();
+
+ // Is the current cursor in the balloon area?
+ bool IsCursorInBalloonCollection() const;
+#endif
+
+ // Queue of active balloons.
+ typedef std::deque<Balloon*> Balloons;
+ Balloons balloons_;
+
+ // The layout parameters for balloons in this collection.
+ Layout layout_;
+
+#if USE_OFFSETS
+ // Factory for generating delayed reposition tasks on mouse motion.
+ ScopedRunnableMethodFactory<BalloonCollectionImpl> reposition_factory_;
+
+ // Is the balloon collection currently listening for UI events?
+ bool added_as_message_loop_observer_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(BalloonCollectionImpl);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_BALLOON_COLLECTION_IMPL_H_
diff --git a/chrome/browser/notifications/balloon_collection_linux.cc b/chrome/browser/notifications/balloon_collection_linux.cc
new file mode 100644
index 0000000..a499292
--- /dev/null
+++ b/chrome/browser/notifications/balloon_collection_linux.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2009 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 "chrome/browser/notifications/balloon_collection_impl.h"
+
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/views/notifications/balloon_view.h"
+#include "gfx/size.h"
+
+Balloon* BalloonCollectionImpl::MakeBalloon(const Notification& notification,
+ Profile* profile) {
+ Balloon* balloon = new Balloon(notification, profile, this);
+
+ balloon->set_view(new BalloonViewImpl(this));
+ gfx::Size size(layout_.min_balloon_width(), layout_.min_balloon_height());
+ balloon->set_content_size(size);
+ return balloon;
+}
+
+int BalloonCollectionImpl::Layout::InterBalloonMargin() const {
+ return 5;
+}
+
+int BalloonCollectionImpl::Layout::HorizontalEdgeMargin() const {
+ return 5;
+}
+
+int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const {
+ return 5;
+}
+
+void BalloonCollectionImpl::DidProcessEvent(GdkEvent* event) {
+ switch (event->type) {
+ case GDK_MOTION_NOTIFY:
+ case GDK_LEAVE_NOTIFY:
+ HandleMouseMoveEvent();
+ break;
+ default:
+ break;
+ }
+}
+
+bool BalloonCollectionImpl::IsCursorInBalloonCollection() const {
+ if (balloons_.empty())
+ return false;
+
+ gfx::Point upper_left = balloons_[balloons_.size() - 1]->GetPosition();
+ gfx::Point lower_right = layout_.GetLayoutOrigin();
+
+ gfx::Rect bounds = gfx::Rect(upper_left.x(),
+ upper_left.y(),
+ lower_right.x() - upper_left.x(),
+ lower_right.y() - upper_left.y());
+
+ GdkScreen* screen = gdk_screen_get_default();
+ GdkDisplay* display = gdk_screen_get_display(screen);
+ gint x, y;
+ gdk_display_get_pointer(display, NULL, &x, &y, NULL);
+ gfx::Point cursor(x, y);
+
+ return bounds.Contains(cursor);
+}
+
+// static
+BalloonCollection* BalloonCollection::Create() {
+ return new BalloonCollectionImpl();
+}
diff --git a/chrome/browser/notifications/balloon_collection_mac.mm b/chrome/browser/notifications/balloon_collection_mac.mm
new file mode 100644
index 0000000..dc4172d
--- /dev/null
+++ b/chrome/browser/notifications/balloon_collection_mac.mm
@@ -0,0 +1,39 @@
+// Copyright (c) 2009 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 "chrome/browser/notifications/balloon_collection_impl.h"
+
+#include "chrome/browser/cocoa/notifications/balloon_view_bridge.h"
+
+Balloon* BalloonCollectionImpl::MakeBalloon(const Notification& notification,
+ Profile* profile) {
+ Balloon* balloon = new Balloon(notification, profile, this);
+ balloon->set_view(new BalloonViewBridge());
+ gfx::Size size(layout_.min_balloon_width(), layout_.min_balloon_height());
+ balloon->set_content_size(size);
+ return balloon;
+}
+
+// static
+gfx::Rect BalloonCollectionImpl::GetMacWorkArea() {
+ NSScreen* primary = [[NSScreen screens] objectAtIndex:0];
+ return gfx::Rect(NSRectToCGRect([primary visibleFrame]));
+}
+
+int BalloonCollectionImpl::Layout::InterBalloonMargin() const {
+ return 5;
+}
+
+int BalloonCollectionImpl::Layout::HorizontalEdgeMargin() const {
+ return 5;
+}
+
+int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const {
+ return 5;
+}
+
+// static
+BalloonCollection* BalloonCollection::Create() {
+ return new BalloonCollectionImpl();
+}
diff --git a/chrome/browser/notifications/balloon_collection_win.cc b/chrome/browser/notifications/balloon_collection_win.cc
new file mode 100644
index 0000000..b5c2351
--- /dev/null
+++ b/chrome/browser/notifications/balloon_collection_win.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2009 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 "chrome/browser/notifications/balloon_collection_impl.h"
+
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/views/notifications/balloon_view.h"
+#include "gfx/rect.h"
+
+Balloon* BalloonCollectionImpl::MakeBalloon(const Notification& notification,
+ Profile* profile) {
+ Balloon* balloon = new Balloon(notification, profile, this);
+ balloon->set_view(new BalloonViewImpl(this));
+ gfx::Size size(layout_.min_balloon_width(), layout_.min_balloon_height());
+ balloon->set_content_size(size);
+ return balloon;
+}
+
+int BalloonCollectionImpl::Layout::InterBalloonMargin() const {
+ return 3;
+}
+
+int BalloonCollectionImpl::Layout::HorizontalEdgeMargin() const {
+ return 2;
+}
+
+int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const {
+ return 0;
+}
+
+void BalloonCollectionImpl::DidProcessMessage(const MSG& msg) {
+ switch (msg.message) {
+ case WM_MOUSEMOVE:
+ case WM_MOUSELEAVE:
+ case WM_NCMOUSELEAVE:
+ HandleMouseMoveEvent();
+ break;
+ }
+}
+
+bool BalloonCollectionImpl::IsCursorInBalloonCollection() const {
+ if (balloons_.empty())
+ return false;
+
+ gfx::Point upper_left = balloons_[balloons_.size() - 1]->GetPosition();
+ gfx::Point lower_right = layout_.GetLayoutOrigin();
+
+ gfx::Rect bounds = gfx::Rect(upper_left.x(),
+ upper_left.y(),
+ lower_right.x() - upper_left.x(),
+ lower_right.y() - upper_left.y());
+
+ DWORD pos = GetMessagePos();
+ gfx::Point cursor(pos);
+
+ return bounds.Contains(cursor);
+}
+
+// static
+BalloonCollection* BalloonCollection::Create() {
+ return new BalloonCollectionImpl();
+}
diff --git a/chrome/browser/notifications/balloon_host.cc b/chrome/browser/notifications/balloon_host.cc
new file mode 100644
index 0000000..e962f5a
--- /dev/null
+++ b/chrome/browser/notifications/balloon_host.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/balloon_host.h"
+
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/in_process_webkit/webkit_context.h"
+#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_preferences_util.h"
+#include "chrome/common/bindings_policy.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/renderer_preferences.h"
+#include "chrome/common/url_constants.h"
+
+BalloonHost::BalloonHost(Balloon* balloon)
+ : render_view_host_(NULL),
+ balloon_(balloon),
+ initialized_(false),
+ should_notify_on_disconnect_(false) {
+ DCHECK(balloon_);
+
+ // If the notification is for an extension URL, make sure to use the extension
+ // process to render it, so that it can communicate with other views in the
+ // extension.
+ const GURL& balloon_url = balloon_->notification().content_url();
+ if (balloon_url.SchemeIs(chrome::kExtensionScheme)) {
+ site_instance_ =
+ balloon_->profile()->GetExtensionProcessManager()->GetSiteInstanceForURL(
+ balloon_url);
+ } else {
+ site_instance_ = SiteInstance::CreateSiteInstance(balloon_->profile());
+ }
+}
+
+void BalloonHost::Shutdown() {
+ if (render_view_host_) {
+ render_view_host_->Shutdown();
+ render_view_host_ = NULL;
+ }
+}
+
+WebPreferences BalloonHost::GetWebkitPrefs() {
+ WebPreferences prefs;
+ prefs.allow_scripts_to_close_windows = true;
+ return prefs;
+}
+
+void BalloonHost::Close(RenderViewHost* render_view_host) {
+ balloon_->CloseByScript();
+ NotifyDisconnect();
+}
+
+void BalloonHost::RenderViewCreated(RenderViewHost* render_view_host) {
+ render_view_host->Send(new ViewMsg_DisableScrollbarsForSmallWindows(
+ render_view_host->routing_id(), balloon_->min_scrollbar_size()));
+ render_view_host->WasResized();
+ render_view_host->EnablePreferredSizeChangedMode(
+ kPreferredSizeWidth | kPreferredSizeHeightThisIsSlow);
+}
+
+void BalloonHost::RenderViewReady(RenderViewHost* render_view_host) {
+ should_notify_on_disconnect_ = true;
+ NotificationService::current()->Notify(
+ NotificationType::NOTIFY_BALLOON_CONNECTED,
+ Source<BalloonHost>(this), NotificationService::NoDetails());
+}
+
+void BalloonHost::RenderViewGone(RenderViewHost* render_view_host) {
+ Close(render_view_host);
+}
+
+void BalloonHost::ProcessDOMUIMessage(const std::string& message,
+ const ListValue* content,
+ const GURL& source_url,
+ int request_id,
+ bool has_callback) {
+ if (extension_function_dispatcher_.get()) {
+ extension_function_dispatcher_->HandleRequest(
+ message, content, source_url, request_id, has_callback);
+ }
+}
+
+// RenderViewHostDelegate::View methods implemented to allow links to
+// open pages in new tabs.
+void BalloonHost::CreateNewWindow(
+ int route_id,
+ WindowContainerType window_container_type,
+ const string16& frame_name) {
+ delegate_view_helper_.CreateNewWindow(
+ route_id,
+ balloon_->profile(),
+ site_instance_.get(),
+ DOMUIFactory::GetDOMUIType(balloon_->notification().content_url()),
+ this,
+ window_container_type,
+ frame_name);
+}
+
+void BalloonHost::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ // Don't allow pop-ups from notifications.
+ if (disposition == NEW_POPUP)
+ return;
+
+ TabContents* contents = delegate_view_helper_.GetCreatedWindow(route_id);
+ if (!contents)
+ return;
+ Browser* browser = BrowserList::GetLastActiveWithProfile(balloon_->profile());
+ if (!browser)
+ return;
+
+ browser->AddTabContents(contents, disposition, initial_pos, user_gesture);
+}
+
+void BalloonHost::UpdatePreferredSize(const gfx::Size& new_size) {
+ balloon_->SetContentPreferredSize(new_size);
+}
+
+RendererPreferences BalloonHost::GetRendererPrefs(Profile* profile) const {
+ RendererPreferences preferences;
+ renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile);
+ return preferences;
+}
+
+void BalloonHost::Init() {
+ DCHECK(!render_view_host_) << "BalloonViewHost already initialized.";
+ int64 session_storage_namespace_id = balloon_->profile()->GetWebKitContext()->
+ dom_storage_context()->AllocateSessionStorageNamespaceId();
+ RenderViewHost* rvh = new RenderViewHost(site_instance_.get(),
+ this, MSG_ROUTING_NONE,
+ session_storage_namespace_id);
+ if (GetProfile()->GetExtensionsService()) {
+ extension_function_dispatcher_.reset(
+ ExtensionFunctionDispatcher::Create(
+ rvh, this, balloon_->notification().content_url()));
+ }
+ if (extension_function_dispatcher_.get()) {
+ rvh->AllowBindings(BindingsPolicy::EXTENSION);
+ rvh->set_is_extension_process(true);
+ }
+
+ // Do platform-specific initialization.
+ render_view_host_ = rvh;
+ InitRenderWidgetHostView();
+ DCHECK(render_widget_host_view());
+
+ rvh->set_view(render_widget_host_view());
+ rvh->CreateRenderView(GetProfile()->GetRequestContext(), string16());
+ rvh->NavigateToURL(balloon_->notification().content_url());
+
+ initialized_ = true;
+}
+
+void BalloonHost::NotifyDisconnect() {
+ if (!should_notify_on_disconnect_)
+ return;
+
+ should_notify_on_disconnect_ = false;
+ NotificationService::current()->Notify(
+ NotificationType::NOTIFY_BALLOON_DISCONNECTED,
+ Source<BalloonHost>(this), NotificationService::NoDetails());
+}
diff --git a/chrome/browser/notifications/balloon_host.h b/chrome/browser/notifications/balloon_host.h
new file mode 100644
index 0000000..07ddf3d
--- /dev/null
+++ b/chrome/browser/notifications/balloon_host.h
@@ -0,0 +1,150 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_BALLOON_HOST_H_
+#define CHROME_BROWSER_NOTIFICATIONS_BALLOON_HOST_H_
+
+#include <string>
+
+#include "chrome/browser/extensions/extension_function_dispatcher.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/renderer_preferences.h"
+#include "webkit/glue/webpreferences.h"
+
+class Browser;
+class Profile;
+
+class BalloonHost : public RenderViewHostDelegate,
+ public RenderViewHostDelegate::View,
+ public ExtensionFunctionDispatcher::Delegate {
+ public:
+ explicit BalloonHost(Balloon* balloon);
+
+ // Initialize the view.
+ void Init();
+
+ // Stops showing the balloon.
+ void Shutdown();
+
+ // ExtensionFunctionDispatcher::Delegate overrides.
+ virtual Browser* GetBrowser() const {
+ // Notifications aren't associated with a particular browser.
+ return NULL;
+ }
+ virtual gfx::NativeView GetNativeViewOfHost() {
+ // TODO(aa): Should this return the native view of the BalloonView*?
+ return NULL;
+ }
+ virtual TabContents* associated_tab_contents() { return NULL; }
+
+ RenderViewHost* render_view_host() const { return render_view_host_; }
+
+ std::wstring GetSource() const {
+ return balloon_->notification().display_source();
+ }
+
+ // RenderViewHostDelegate overrides.
+ virtual WebPreferences GetWebkitPrefs();
+ virtual SiteInstance* GetSiteInstance() const {
+ return site_instance_.get();
+ }
+ virtual Profile* GetProfile() const { return balloon_->profile(); }
+ virtual const GURL& GetURL() const {
+ return balloon_->notification().content_url();
+ }
+ virtual void Close(RenderViewHost* render_view_host);
+ virtual void RenderViewCreated(RenderViewHost* render_view_host);
+ virtual void RenderViewReady(RenderViewHost* render_view_host);
+ virtual void RenderViewGone(RenderViewHost* render_view_host);
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id, const std::wstring& title) {}
+ virtual int GetBrowserWindowID() const {
+ return extension_misc::kUnknownWindowId;
+ }
+ virtual ViewType::Type GetRenderViewType() const {
+ return ViewType::NOTIFICATION;
+ }
+ virtual RenderViewHostDelegate::View* GetViewDelegate() {
+ return this;
+ }
+ virtual void ProcessDOMUIMessage(const std::string& message,
+ const ListValue* content,
+ const GURL& source_url,
+ int request_id,
+ bool has_callback);
+
+ // RenderViewHostDelegate::View methods. Only the ones for opening new
+ // windows are currently implemented.
+ virtual void CreateNewWindow(
+ int route_id,
+ WindowContainerType window_container_type,
+ const string16& frame_name);
+ virtual void CreateNewWidget(int route_id, WebKit::WebPopupType popup_type) {}
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {}
+ virtual void ShowContextMenu(const ContextMenuParams& params) {}
+ virtual void StartDragging(const WebDropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops) {}
+ virtual void StartDragging(const WebDropData&,
+ WebKit::WebDragOperationsMask,
+ const SkBitmap&,
+ const gfx::Point&) {}
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) {}
+ virtual void GotFocus() {}
+ virtual void TakeFocus(bool reverse) {}
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return false;
+ }
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {}
+ virtual void HandleMouseEvent() {}
+ virtual void HandleMouseLeave() {}
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size);
+ virtual RendererPreferences GetRendererPrefs(Profile* profile) const;
+
+ protected:
+ // Must override in platform specific implementations.
+ virtual void InitRenderWidgetHostView() = 0;
+ virtual RenderWidgetHostView* render_widget_host_view() const = 0;
+
+ // Owned pointer to the host for the renderer process.
+ RenderViewHost* render_view_host_;
+
+ private:
+ // Called to send an event that the balloon has been disconnected from
+ // a renderer (if should_notify_on_disconnect_ is true).
+ void NotifyDisconnect();
+
+ // Non-owned pointer to the associated balloon.
+ Balloon* balloon_;
+
+ // True after Init() has completed.
+ bool initialized_;
+
+ // Indicates whether we should notify about disconnection of this balloon.
+ // This is used to ensure disconnection notifications only happen if
+ // a connection notification has happened and that they happen only once.
+ bool should_notify_on_disconnect_;
+
+ // Site instance for the balloon/profile, to be used for opening new links.
+ scoped_refptr<SiteInstance> site_instance_;
+
+ // Common implementations of some RenderViewHostDelegate::View methods.
+ RenderViewHostDelegateViewHelper delegate_view_helper_;
+
+ // Handles requests to extension APIs. Will only be non-NULL if we are
+ // rendering a page from an extension.
+ scoped_ptr<ExtensionFunctionDispatcher> extension_function_dispatcher_;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_BALLOON_HOST_H_
diff --git a/chrome/browser/notifications/desktop_notification_service.cc b/chrome/browser/notifications/desktop_notification_service.cc
new file mode 100644
index 0000000..bc66077
--- /dev/null
+++ b/chrome/browser/notifications/desktop_notification_service.cc
@@ -0,0 +1,582 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/desktop_notification_service.h"
+
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/thread.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_child_process_host.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/notifications/notification_object_proxy.h"
+#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chrome/browser/notifications/notifications_prefs_cache.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/scoped_pref_update.h"
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/worker_host/worker_process_host.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/url_constants.h"
+#include "grit/browser_resources.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "net/base/escape.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebNotificationPresenter.h"
+
+using WebKit::WebNotificationPresenter;
+using WebKit::WebTextDirection;
+
+const ContentSetting kDefaultSetting = CONTENT_SETTING_ASK;
+
+// static
+string16 DesktopNotificationService::CreateDataUrl(
+ const GURL& icon_url, const string16& title, const string16& body,
+ WebTextDirection dir) {
+ int resource;
+ string16 line_name;
+ string16 line;
+ std::vector<std::string> subst;
+ if (icon_url.is_valid()) {
+ resource = IDR_NOTIFICATION_ICON_HTML;
+ subst.push_back(icon_url.spec());
+ subst.push_back(EscapeForHTML(UTF16ToUTF8(title)));
+ subst.push_back(EscapeForHTML(UTF16ToUTF8(body)));
+ // icon float position
+ subst.push_back(dir == WebKit::WebTextDirectionRightToLeft ?
+ "right" : "left");
+ } else if (title.empty() || body.empty()) {
+ resource = IDR_NOTIFICATION_1LINE_HTML;
+ line = title.empty() ? body : title;
+ // Strings are div names in the template file.
+ line_name = title.empty() ? ASCIIToUTF16("description")
+ : ASCIIToUTF16("title");
+ subst.push_back(EscapeForHTML(UTF16ToUTF8(line_name)));
+ subst.push_back(EscapeForHTML(UTF16ToUTF8(line)));
+ } else {
+ resource = IDR_NOTIFICATION_2LINE_HTML;
+ subst.push_back(EscapeForHTML(UTF16ToUTF8(title)));
+ subst.push_back(EscapeForHTML(UTF16ToUTF8(body)));
+ }
+ // body text direction
+ subst.push_back(dir == WebKit::WebTextDirectionRightToLeft ?
+ "rtl" : "ltr");
+
+ const base::StringPiece template_html(
+ ResourceBundle::GetSharedInstance().GetRawDataResource(
+ resource));
+
+ if (template_html.empty()) {
+ NOTREACHED() << "unable to load template. ID: " << resource;
+ return string16();
+ }
+
+ std::string data = ReplaceStringPlaceholders(template_html, subst, NULL);
+ return UTF8ToUTF16("data:text/html;charset=utf-8," +
+ EscapeQueryParamValue(data, false));
+}
+
+// A task object which calls the renderer to inform the web page that the
+// permission request has completed.
+class NotificationPermissionCallbackTask : public Task {
+ public:
+ NotificationPermissionCallbackTask(int process_id, int route_id,
+ int request_id)
+ : process_id_(process_id),
+ route_id_(route_id),
+ request_id_(request_id) {
+ }
+
+ virtual void Run() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+ RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_);
+ if (host)
+ host->Send(new ViewMsg_PermissionRequestDone(route_id_, request_id_));
+ }
+
+ private:
+ int process_id_;
+ int route_id_;
+ int request_id_;
+};
+
+// The delegate for the infobar shown when an origin requests notification
+// permissions.
+class NotificationPermissionInfoBarDelegate : public ConfirmInfoBarDelegate {
+ public:
+ NotificationPermissionInfoBarDelegate(TabContents* contents,
+ const GURL& origin,
+ const std::wstring& display_name,
+ int process_id,
+ int route_id,
+ int callback_context)
+ : ConfirmInfoBarDelegate(contents),
+ origin_(origin),
+ display_name_(display_name),
+ profile_(contents->profile()),
+ process_id_(process_id),
+ route_id_(route_id),
+ callback_context_(callback_context),
+ action_taken_(false) {
+ }
+
+ // Overridden from ConfirmInfoBarDelegate:
+ virtual void InfoBarClosed() {
+ if (!action_taken_)
+ UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Ignored", 1);
+
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ new NotificationPermissionCallbackTask(
+ process_id_, route_id_, callback_context_));
+
+ delete this;
+ }
+
+ virtual std::wstring GetMessageText() const {
+ return l10n_util::GetStringF(IDS_NOTIFICATION_PERMISSIONS, display_name_);
+ }
+
+ virtual SkBitmap* GetIcon() const {
+ return ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_PRODUCT_ICON_32);
+ }
+
+ virtual int GetButtons() const {
+ return BUTTON_OK | BUTTON_CANCEL | BUTTON_OK_DEFAULT;
+ }
+
+ virtual std::wstring GetButtonLabel(InfoBarButton button) const {
+ return button == BUTTON_OK ?
+ l10n_util::GetString(IDS_NOTIFICATION_PERMISSION_YES) :
+ l10n_util::GetString(IDS_NOTIFICATION_PERMISSION_NO);
+ }
+
+ virtual bool Accept() {
+ UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Allowed", 1);
+ profile_->GetDesktopNotificationService()->GrantPermission(origin_);
+ action_taken_ = true;
+ return true;
+ }
+
+ virtual bool Cancel() {
+ UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Denied", 1);
+ profile_->GetDesktopNotificationService()->DenyPermission(origin_);
+ action_taken_ = true;
+ return true;
+ }
+
+ private:
+ // The origin we are asking for permissions on.
+ GURL origin_;
+
+ // The display name for the origin to be displayed. Will be different from
+ // origin_ for extensions.
+ std::wstring display_name_;
+
+ // The Profile that we restore sessions from.
+ Profile* profile_;
+
+ // The callback information that tells us how to respond to javascript via
+ // the correct RenderView.
+ int process_id_;
+ int route_id_;
+ int callback_context_;
+
+ // Whether the user clicked one of the buttons.
+ bool action_taken_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationPermissionInfoBarDelegate);
+};
+
+DesktopNotificationService::DesktopNotificationService(Profile* profile,
+ NotificationUIManager* ui_manager)
+ : profile_(profile),
+ ui_manager_(ui_manager) {
+ InitPrefs();
+ StartObserving();
+}
+
+DesktopNotificationService::~DesktopNotificationService() {
+ StopObserving();
+}
+
+void DesktopNotificationService::RegisterUserPrefs(PrefService* user_prefs) {
+ if (!user_prefs->FindPreference(
+ prefs::kDesktopNotificationDefaultContentSetting)) {
+ user_prefs->RegisterIntegerPref(
+ prefs::kDesktopNotificationDefaultContentSetting, kDefaultSetting);
+ }
+ if (!user_prefs->FindPreference(prefs::kDesktopNotificationAllowedOrigins))
+ user_prefs->RegisterListPref(prefs::kDesktopNotificationAllowedOrigins);
+ if (!user_prefs->FindPreference(prefs::kDesktopNotificationDeniedOrigins))
+ user_prefs->RegisterListPref(prefs::kDesktopNotificationDeniedOrigins);
+}
+
+// Initialize the cache with the allowed and denied origins, or
+// create the preferences if they don't exist yet.
+void DesktopNotificationService::InitPrefs() {
+ PrefService* prefs = profile_->GetPrefs();
+ std::vector<GURL> allowed_origins;
+ std::vector<GURL> denied_origins;
+ ContentSetting default_content_setting = CONTENT_SETTING_DEFAULT;
+
+ if (!profile_->IsOffTheRecord()) {
+ default_content_setting = IntToContentSetting(
+ prefs->GetInteger(prefs::kDesktopNotificationDefaultContentSetting));
+ allowed_origins = GetAllowedOrigins();
+ denied_origins = GetBlockedOrigins();
+ }
+
+ prefs_cache_ = new NotificationsPrefsCache();
+ prefs_cache_->SetCacheDefaultContentSetting(default_content_setting);
+ prefs_cache_->SetCacheAllowedOrigins(allowed_origins);
+ prefs_cache_->SetCacheDeniedOrigins(denied_origins);
+ prefs_cache_->set_is_initialized(true);
+}
+
+void DesktopNotificationService::StartObserving() {
+ if (!profile_->IsOffTheRecord()) {
+ PrefService* prefs = profile_->GetPrefs();
+ prefs->AddPrefObserver(prefs::kDesktopNotificationDefaultContentSetting,
+ this);
+ prefs->AddPrefObserver(prefs::kDesktopNotificationAllowedOrigins, this);
+ prefs->AddPrefObserver(prefs::kDesktopNotificationDeniedOrigins, this);
+ }
+}
+
+void DesktopNotificationService::StopObserving() {
+ if (!profile_->IsOffTheRecord()) {
+ PrefService* prefs = profile_->GetPrefs();
+ prefs->RemovePrefObserver(prefs::kDesktopNotificationDefaultContentSetting,
+ this);
+ prefs->RemovePrefObserver(prefs::kDesktopNotificationAllowedOrigins, this);
+ prefs->RemovePrefObserver(prefs::kDesktopNotificationDeniedOrigins, this);
+ }
+}
+
+void DesktopNotificationService::GrantPermission(const GURL& origin) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ PersistPermissionChange(origin, true);
+
+ // Schedule a cache update on the IO thread.
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ prefs_cache_.get(), &NotificationsPrefsCache::CacheAllowedOrigin,
+ origin));
+}
+
+void DesktopNotificationService::DenyPermission(const GURL& origin) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ PersistPermissionChange(origin, false);
+
+ // Schedule a cache update on the IO thread.
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ prefs_cache_.get(), &NotificationsPrefsCache::CacheDeniedOrigin,
+ origin));
+}
+
+void DesktopNotificationService::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(NotificationType::PREF_CHANGED == type);
+ PrefService* prefs = profile_->GetPrefs();
+ std::wstring* name = Details<std::wstring>(details).ptr();
+
+ if (0 == name->compare(prefs::kDesktopNotificationAllowedOrigins)) {
+ std::vector<GURL> allowed_origins(GetAllowedOrigins());
+ // Schedule a cache update on the IO thread.
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ prefs_cache_.get(),
+ &NotificationsPrefsCache::SetCacheAllowedOrigins,
+ allowed_origins));
+ } else if (0 == name->compare(prefs::kDesktopNotificationDeniedOrigins)) {
+ std::vector<GURL> denied_origins(GetBlockedOrigins());
+ // Schedule a cache update on the IO thread.
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ prefs_cache_.get(),
+ &NotificationsPrefsCache::SetCacheDeniedOrigins,
+ denied_origins));
+ } else if (0 == name->compare(
+ prefs::kDesktopNotificationDefaultContentSetting)) {
+ const ContentSetting default_content_setting = IntToContentSetting(
+ prefs->GetInteger(prefs::kDesktopNotificationDefaultContentSetting));
+
+ // Schedule a cache update on the IO thread.
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ prefs_cache_.get(),
+ &NotificationsPrefsCache::SetCacheDefaultContentSetting,
+ default_content_setting));
+ }
+}
+
+void DesktopNotificationService::PersistPermissionChange(
+ const GURL& origin, bool is_allowed) {
+ // Don't persist changes when off the record.
+ if (profile_->IsOffTheRecord())
+ return;
+
+ PrefService* prefs = profile_->GetPrefs();
+
+ // |Observe()| updates the whole permission set in the cache, but only a
+ // single origin has changed. Hence, callers of this method manually
+ // schedule a task to update the prefs cache, and the prefs observer is
+ // disabled while the update runs.
+ StopObserving();
+
+ bool allowed_changed = false;
+ bool denied_changed = false;
+
+ ListValue* allowed_sites =
+ prefs->GetMutableList(prefs::kDesktopNotificationAllowedOrigins);
+ ListValue* denied_sites =
+ prefs->GetMutableList(prefs::kDesktopNotificationDeniedOrigins);
+ {
+ // value is passed to the preferences list, or deleted.
+ StringValue* value = new StringValue(origin.spec());
+
+ // Remove from one list and add to the other.
+ if (is_allowed) {
+ // Remove from the denied list.
+ if (denied_sites->Remove(*value) != -1)
+ denied_changed = true;
+
+ // Add to the allowed list.
+ if (allowed_sites->AppendIfNotPresent(value))
+ allowed_changed = true;
+ else
+ delete value;
+ } else {
+ // Remove from the allowed list.
+ if (allowed_sites->Remove(*value) != -1)
+ allowed_changed = true;
+
+ // Add to the denied list.
+ if (denied_sites->AppendIfNotPresent(value))
+ denied_changed = true;
+ else
+ delete value;
+ }
+ }
+
+ // Persist the pref if anthing changed, but only send updates for the
+ // list that changed.
+ if (allowed_changed || denied_changed) {
+ if (allowed_changed) {
+ ScopedPrefUpdate update_allowed(
+ prefs, prefs::kDesktopNotificationAllowedOrigins);
+ }
+ if (denied_changed) {
+ ScopedPrefUpdate updateDenied(
+ prefs, prefs::kDesktopNotificationDeniedOrigins);
+ }
+ prefs->ScheduleSavePersistentPrefs();
+ }
+ StartObserving();
+}
+
+ContentSetting DesktopNotificationService::GetDefaultContentSetting() {
+ PrefService* prefs = profile_->GetPrefs();
+ ContentSetting setting = IntToContentSetting(
+ prefs->GetInteger(prefs::kDesktopNotificationDefaultContentSetting));
+ if (setting == CONTENT_SETTING_DEFAULT)
+ setting = kDefaultSetting;
+ return setting;
+}
+
+void DesktopNotificationService::SetDefaultContentSetting(
+ ContentSetting setting) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ profile_->GetPrefs()->SetInteger(
+ prefs::kDesktopNotificationDefaultContentSetting,
+ setting == CONTENT_SETTING_DEFAULT ? kDefaultSetting : setting);
+ // The cache is updated through the notification observer.
+}
+
+std::vector<GURL> DesktopNotificationService::GetAllowedOrigins() {
+ std::vector<GURL> allowed_origins;
+ PrefService* prefs = profile_->GetPrefs();
+ const ListValue* allowed_sites =
+ prefs->GetList(prefs::kDesktopNotificationAllowedOrigins);
+ if (allowed_sites) {
+ NotificationsPrefsCache::ListValueToGurlVector(*allowed_sites,
+ &allowed_origins);
+ }
+ return allowed_origins;
+}
+
+std::vector<GURL> DesktopNotificationService::GetBlockedOrigins() {
+ std::vector<GURL> denied_origins;
+ PrefService* prefs = profile_->GetPrefs();
+ const ListValue* denied_sites =
+ prefs->GetList(prefs::kDesktopNotificationDeniedOrigins);
+ if (denied_sites) {
+ NotificationsPrefsCache::ListValueToGurlVector(*denied_sites,
+ &denied_origins);
+ }
+ return denied_origins;
+}
+
+void DesktopNotificationService::ResetAllowedOrigin(const GURL& origin) {
+ if (profile_->IsOffTheRecord())
+ return;
+
+ // Since this isn't called often, let the normal observer behavior update the
+ // cache in this case.
+ PrefService* prefs = profile_->GetPrefs();
+ ListValue* allowed_sites =
+ prefs->GetMutableList(prefs::kDesktopNotificationAllowedOrigins);
+ {
+ StringValue value(origin.spec());
+ int removed_index = allowed_sites->Remove(value);
+ DCHECK_NE(-1, removed_index) << origin << " was not allowed";
+ ScopedPrefUpdate update_allowed(
+ prefs, prefs::kDesktopNotificationAllowedOrigins);
+ }
+ prefs->ScheduleSavePersistentPrefs();
+}
+
+void DesktopNotificationService::ResetBlockedOrigin(const GURL& origin) {
+ if (profile_->IsOffTheRecord())
+ return;
+
+ // Since this isn't called often, let the normal observer behavior update the
+ // cache in this case.
+ PrefService* prefs = profile_->GetPrefs();
+ ListValue* denied_sites =
+ prefs->GetMutableList(prefs::kDesktopNotificationDeniedOrigins);
+ {
+ StringValue value(origin.spec());
+ int removed_index = denied_sites->Remove(value);
+ DCHECK_NE(-1, removed_index) << origin << " was not blocked";
+ ScopedPrefUpdate update_allowed(
+ prefs, prefs::kDesktopNotificationDeniedOrigins);
+ }
+ prefs->ScheduleSavePersistentPrefs();
+}
+
+void DesktopNotificationService::ResetAllOrigins() {
+ PrefService* prefs = profile_->GetPrefs();
+ prefs->ClearPref(prefs::kDesktopNotificationAllowedOrigins);
+ prefs->ClearPref(prefs::kDesktopNotificationDeniedOrigins);
+}
+
+ContentSetting DesktopNotificationService::GetContentSetting(
+ const GURL& origin) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ if (profile_->IsOffTheRecord())
+ return kDefaultSetting;
+
+ std::vector<GURL> allowed_origins(GetAllowedOrigins());
+ if (std::find(allowed_origins.begin(), allowed_origins.end(), origin) !=
+ allowed_origins.end())
+ return CONTENT_SETTING_ALLOW;
+
+ std::vector<GURL> denied_origins(GetBlockedOrigins());
+ if (std::find(denied_origins.begin(), denied_origins.end(), origin) !=
+ denied_origins.end())
+ return CONTENT_SETTING_BLOCK;
+
+ return GetDefaultContentSetting();
+}
+
+void DesktopNotificationService::RequestPermission(
+ const GURL& origin, int process_id, int route_id, int callback_context,
+ TabContents* tab) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ if (!tab)
+ return;
+
+ // If |origin| hasn't been seen before and the default content setting for
+ // notifications is "ask", show an infobar.
+ // The cache can only answer queries on the IO thread once it's initialized,
+ // so don't ask the cache.
+ ContentSetting setting = GetContentSetting(origin);
+ if (setting == CONTENT_SETTING_ASK) {
+ // Show an info bar requesting permission.
+ std::wstring display_name = DisplayNameForOrigin(origin);
+
+ tab->AddInfoBar(new NotificationPermissionInfoBarDelegate(
+ tab, origin, display_name, process_id, route_id, callback_context));
+ } else {
+ // Notify renderer immediately.
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ new NotificationPermissionCallbackTask(
+ process_id, route_id, callback_context));
+ }
+}
+
+void DesktopNotificationService::ShowNotification(
+ const Notification& notification) {
+ ui_manager_->Add(notification, profile_);
+}
+
+bool DesktopNotificationService::CancelDesktopNotification(
+ int process_id, int route_id, int notification_id) {
+ scoped_refptr<NotificationObjectProxy> proxy(
+ new NotificationObjectProxy(process_id, route_id, notification_id,
+ false));
+ // TODO(johnnyg): clean up this "empty" notification.
+ Notification notif(GURL(), GURL(), L"", ASCIIToUTF16(""), proxy);
+ return ui_manager_->Cancel(notif);
+}
+
+
+bool DesktopNotificationService::ShowDesktopNotification(
+ const ViewHostMsg_ShowNotification_Params& params,
+ int process_id, int route_id, DesktopNotificationSource source) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ const GURL& origin = params.origin;
+ NotificationObjectProxy* proxy =
+ new NotificationObjectProxy(process_id, route_id,
+ params.notification_id,
+ source == WorkerNotification);
+ GURL contents;
+ if (params.is_html) {
+ contents = params.contents_url;
+ } else {
+ // "upconvert" the string parameters to a data: URL.
+ contents = GURL(
+ CreateDataUrl(params.icon_url, params.title, params.body,
+ params.direction));
+ }
+ Notification notif(
+ origin, contents, DisplayNameForOrigin(origin), params.replace_id, proxy);
+ ShowNotification(notif);
+ return true;
+}
+
+std::wstring DesktopNotificationService::DisplayNameForOrigin(
+ const GURL& origin) {
+ // If the source is an extension, lookup the display name.
+ if (origin.SchemeIs(chrome::kExtensionScheme)) {
+ ExtensionsService* ext_service = profile_->GetExtensionsService();
+ if (ext_service) {
+ Extension* extension = ext_service->GetExtensionByURL(origin);
+ if (extension)
+ return UTF8ToWide(extension->name());
+ }
+ }
+ return UTF8ToWide(origin.host());
+}
diff --git a/chrome/browser/notifications/desktop_notification_service.h b/chrome/browser/notifications/desktop_notification_service.h
new file mode 100644
index 0000000..ae38b18
--- /dev/null
+++ b/chrome/browser/notifications/desktop_notification_service.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATION_SERVICE_H_
+#define CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATION_SERVICE_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/common/content_settings.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebTextDirection.h"
+
+class NotificationUIManager;
+class NotificationsPrefsCache;
+class PrefService;
+class Profile;
+class Task;
+class TabContents;
+struct ViewHostMsg_ShowNotification_Params;
+
+// The DesktopNotificationService is an object, owned by the Profile,
+// which provides the creation of desktop "toasts" to web pages and workers.
+class DesktopNotificationService : public NotificationObserver {
+ public:
+ enum DesktopNotificationSource {
+ PageNotification,
+ WorkerNotification
+ };
+
+ DesktopNotificationService(Profile* profile,
+ NotificationUIManager* ui_manager);
+ virtual ~DesktopNotificationService();
+
+ // Requests permission (using an info-bar) for a given origin.
+ // |callback_context| contains an opaque value to pass back to the
+ // requesting process when the info-bar finishes.
+ void RequestPermission(const GURL& origin,
+ int process_id,
+ int route_id,
+ int callback_context,
+ TabContents* tab);
+
+ // ShowNotification is called on the UI thread handling IPCs from a child
+ // process, identified by |process_id| and |route_id|. |source| indicates
+ // whether the script is in a worker or page. |params| contains all the
+ // other parameters supplied by the worker or page.
+ bool ShowDesktopNotification(
+ const ViewHostMsg_ShowNotification_Params& params,
+ int process_id, int route_id, DesktopNotificationSource source);
+
+ // Cancels a notification. If it has already been shown, it will be
+ // removed from the screen. If it hasn't been shown yet, it won't be
+ // shown.
+ bool CancelDesktopNotification(int process_id,
+ int route_id,
+ int notification_id);
+
+ // Methods to setup and modify permission preferences.
+ void GrantPermission(const GURL& origin);
+ void DenyPermission(const GURL& origin);
+
+ // NotificationObserver implementation.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ NotificationsPrefsCache* prefs_cache() { return prefs_cache_; }
+
+ // Creates a data:xxxx URL which contains the full HTML for a notification
+ // using supplied icon, title, and text, run through a template which contains
+ // the standard formatting for notifications.
+ static string16 CreateDataUrl(const GURL& icon_url,
+ const string16& title,
+ const string16& body,
+ WebKit::WebTextDirection dir);
+
+ // The default content setting determines how to handle origins that haven't
+ // been allowed or denied yet.
+ ContentSetting GetDefaultContentSetting();
+ void SetDefaultContentSetting(ContentSetting setting);
+
+ // Returns all origins that explicitly have been allowed.
+ std::vector<GURL> GetAllowedOrigins();
+
+ // Returns all origins that explicitly have been denied.
+ std::vector<GURL> GetBlockedOrigins();
+
+ // Removes an origin from the "explicitly allowed" set.
+ void ResetAllowedOrigin(const GURL& origin);
+
+ // Removes an origin from the "explicitly denied" set.
+ void ResetBlockedOrigin(const GURL& origin);
+
+ // Clears the sets of explicitly allowed and denied origins.
+ void ResetAllOrigins();
+
+ static void RegisterUserPrefs(PrefService* user_prefs);
+ private:
+ void InitPrefs();
+ void StartObserving();
+ void StopObserving();
+
+ // Takes a notification object and shows it in the UI.
+ void ShowNotification(const Notification& notification);
+
+ // Save a permission change to the profile.
+ void PersistPermissionChange(const GURL& origin, bool is_allowed);
+
+ // Returns a display name for an origin, to be used in permission infobar
+ // or on the frame of the notification toast. Different from the origin
+ // itself when dealing with extensions.
+ std::wstring DisplayNameForOrigin(const GURL& origin);
+
+ ContentSetting GetContentSetting(const GURL& origin);
+
+ // The profile which owns this object.
+ Profile* profile_;
+
+ // A cache of preferences which is accessible only on the IO thread
+ // to service synchronous IPCs.
+ scoped_refptr<NotificationsPrefsCache> prefs_cache_;
+
+ // Non-owned pointer to the notification manager which manages the
+ // UI for desktop toasts.
+ NotificationUIManager* ui_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopNotificationService);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATION_SERVICE_H_
diff --git a/chrome/browser/notifications/desktop_notification_service_unittest.cc b/chrome/browser/notifications/desktop_notification_service_unittest.cc
new file mode 100644
index 0000000..3af8b0f
--- /dev/null
+++ b/chrome/browser/notifications/desktop_notification_service_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/desktop_notification_service.h"
+
+#include "base/ref_counted.h"
+#include "base/waitable_event.h"
+#include "chrome/browser/notifications/notifications_prefs_cache.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/renderer_host/test/test_render_view_host.h"
+#include "chrome/browser/scoped_pref_update.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/testing_profile.h"
+#include "grit/generated_resources.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebNotificationPresenter.h"
+
+namespace {
+
+// NotificationsPrefsCache wants to be called on the IO thread. This class
+// routes calls to the cache on the IO thread.
+class ThreadProxy : public base::RefCountedThreadSafe<ThreadProxy> {
+ public:
+ ThreadProxy()
+ : io_event_(false, false),
+ ui_event_(false, false) {
+ // The current message loop was already initalized by the test superclass.
+ ui_thread_.reset(
+ new ChromeThread(ChromeThread::UI, MessageLoop::current()));
+
+ // Create IO thread, start its message loop.
+ io_thread_.reset(new ChromeThread(ChromeThread::IO));
+ io_thread_->Start();
+
+ // Calling PauseIOThread() here isn't safe, because the runnable method
+ // could complete before the constructor is done, deleting |this|.
+ }
+
+ int CacheHasPermission(NotificationsPrefsCache* cache, const GURL& url) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ ChromeThread::PostTask(ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this, &ThreadProxy::CacheHasPermissionIO,
+ cache, url));
+ io_event_.Signal();
+ ui_event_.Wait(); // Wait for IO thread to be done.
+ ChromeThread::PostTask(ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this, &ThreadProxy::PauseIOThreadIO));
+
+ return permission_;
+ }
+
+ void PauseIOThread() {
+ ChromeThread::PostTask(ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this, &ThreadProxy::PauseIOThreadIO));
+ }
+
+ void DrainIOThread() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ io_event_.Signal();
+ io_thread_->Stop();
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ThreadProxy>;
+ ~ThreadProxy() {
+ DrainIOThread();
+ }
+
+ void PauseIOThreadIO() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+ io_event_.Wait();
+ }
+
+ void CacheHasPermissionIO(NotificationsPrefsCache* cache, const GURL& url) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+ permission_ = cache->HasPermission(url);
+ ui_event_.Signal();
+ }
+
+ base::WaitableEvent io_event_;
+ base::WaitableEvent ui_event_;
+ scoped_ptr<ChromeThread> ui_thread_;
+ scoped_ptr<ChromeThread> io_thread_;
+
+ int permission_;
+};
+
+
+class DesktopNotificationServiceTest : public RenderViewHostTestHarness {
+ public:
+ DesktopNotificationServiceTest() {
+ }
+
+ virtual void SetUp() {
+ RenderViewHostTestHarness::SetUp();
+ proxy_ = new ThreadProxy;
+ proxy_->PauseIOThread();
+
+ // Creates the service, calls InitPrefs() on it which loads data from the
+ // profile into the cache and then puts the cache in io thread mode.
+ service_ = profile()->GetDesktopNotificationService();
+ cache_ = service_->prefs_cache();
+ }
+
+ virtual void TearDown() {
+ // The io thread's waiting on the io_event_ might hold a ref to |proxy_|,
+ // preventing its destruction. Clear that ref.
+ proxy_->DrainIOThread();
+ RenderViewHostTestHarness::TearDown();
+ }
+
+ DesktopNotificationService* service_;
+ NotificationsPrefsCache* cache_;
+ scoped_refptr<ThreadProxy> proxy_;
+};
+
+TEST_F(DesktopNotificationServiceTest, DefaultContentSettingSentToCache) {
+ // The default pref registered in DesktopNotificationService is "ask",
+ // and that's what sent to the cache.
+ EXPECT_EQ(CONTENT_SETTING_ASK, cache_->CachedDefaultContentSetting());
+
+ // Change the default content setting. This will post a task on the IO thread
+ // to update the cache.
+ service_->SetDefaultContentSetting(CONTENT_SETTING_BLOCK);
+
+ // The updated pref shouldn't be sent to the cache immediately.
+ EXPECT_EQ(CONTENT_SETTING_ASK, cache_->CachedDefaultContentSetting());
+
+ // Run IO thread tasks.
+ proxy_->DrainIOThread();
+
+ // Now that IO thread events have been processed, it should be there.
+ EXPECT_EQ(CONTENT_SETTING_BLOCK, cache_->CachedDefaultContentSetting());
+}
+
+TEST_F(DesktopNotificationServiceTest, GrantPermissionSentToCache) {
+ GURL url("http://allowed.com");
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionNotAllowed,
+ proxy_->CacheHasPermission(cache_, url));
+
+ service_->GrantPermission(url);
+
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionAllowed,
+ proxy_->CacheHasPermission(cache_, url));
+}
+
+TEST_F(DesktopNotificationServiceTest, DenyPermissionSentToCache) {
+ GURL url("http://denied.com");
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionNotAllowed,
+ proxy_->CacheHasPermission(cache_, url));
+
+ service_->DenyPermission(url);
+
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionDenied,
+ proxy_->CacheHasPermission(cache_, url));
+}
+
+TEST_F(DesktopNotificationServiceTest, PrefChangesSentToCache) {
+ PrefService* prefs = profile()->GetPrefs();
+
+ ListValue* allowed_sites =
+ prefs->GetMutableList(prefs::kDesktopNotificationAllowedOrigins);
+ {
+ allowed_sites->Append(new StringValue(GURL("http://allowed.com").spec()));
+ ScopedPrefUpdate updateAllowed(
+ prefs, prefs::kDesktopNotificationAllowedOrigins);
+ }
+
+ ListValue* denied_sites =
+ prefs->GetMutableList(prefs::kDesktopNotificationDeniedOrigins);
+ {
+ denied_sites->Append(new StringValue(GURL("http://denied.com").spec()));
+ ScopedPrefUpdate updateDenied(
+ prefs, prefs::kDesktopNotificationDeniedOrigins);
+ }
+
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionAllowed,
+ proxy_->CacheHasPermission(cache_, GURL("http://allowed.com")));
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionDenied,
+ proxy_->CacheHasPermission(cache_, GURL("http://denied.com")));
+}
+
+TEST_F(DesktopNotificationServiceTest, GetAllowedOrigins) {
+ service_->GrantPermission(GURL("http://allowed2.com"));
+ service_->GrantPermission(GURL("http://allowed.com"));
+
+ std::vector<GURL> allowed_origins(service_->GetAllowedOrigins());
+ ASSERT_EQ(2u, allowed_origins.size());
+ EXPECT_EQ(GURL("http://allowed2.com"), allowed_origins[0]);
+ EXPECT_EQ(GURL("http://allowed.com"), allowed_origins[1]);
+}
+
+TEST_F(DesktopNotificationServiceTest, GetBlockedOrigins) {
+ service_->DenyPermission(GURL("http://denied2.com"));
+ service_->DenyPermission(GURL("http://denied.com"));
+
+ std::vector<GURL> denied_origins(service_->GetBlockedOrigins());
+ ASSERT_EQ(2u, denied_origins.size());
+ EXPECT_EQ(GURL("http://denied2.com"), denied_origins[0]);
+ EXPECT_EQ(GURL("http://denied.com"), denied_origins[1]);
+}
+
+TEST_F(DesktopNotificationServiceTest, ResetAllSentToCache) {
+ GURL allowed_url("http://allowed.com");
+ service_->GrantPermission(allowed_url);
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionAllowed,
+ proxy_->CacheHasPermission(cache_, allowed_url));
+ GURL denied_url("http://denied.com");
+ service_->DenyPermission(denied_url);
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionDenied,
+ proxy_->CacheHasPermission(cache_, denied_url));
+
+ service_->ResetAllOrigins();
+
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionNotAllowed,
+ proxy_->CacheHasPermission(cache_, allowed_url));
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionNotAllowed,
+ proxy_->CacheHasPermission(cache_, denied_url));
+}
+
+TEST_F(DesktopNotificationServiceTest, ResetAllowedSentToCache) {
+ GURL allowed_url("http://allowed.com");
+ service_->GrantPermission(allowed_url);
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionAllowed,
+ proxy_->CacheHasPermission(cache_, allowed_url));
+
+ service_->ResetAllowedOrigin(allowed_url);
+
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionNotAllowed,
+ proxy_->CacheHasPermission(cache_, allowed_url));
+}
+
+TEST_F(DesktopNotificationServiceTest, ResetBlockedSentToCache) {
+ GURL denied_url("http://denied.com");
+ service_->DenyPermission(denied_url);
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionDenied,
+ proxy_->CacheHasPermission(cache_, denied_url));
+
+ service_->ResetBlockedOrigin(denied_url);
+
+ EXPECT_EQ(WebKit::WebNotificationPresenter::PermissionNotAllowed,
+ proxy_->CacheHasPermission(cache_, denied_url));
+}
+
+} // namespace
diff --git a/chrome/browser/notifications/desktop_notifications_unittest.cc b/chrome/browser/notifications/desktop_notifications_unittest.cc
new file mode 100644
index 0000000..34746d1
--- /dev/null
+++ b/chrome/browser/notifications/desktop_notifications_unittest.cc
@@ -0,0 +1,327 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/desktop_notifications_unittest.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+
+// static
+const int MockBalloonCollection::kMockBalloonSpace = 5;
+
+// static
+std::string DesktopNotificationsTest::log_output_;
+
+void MockBalloonCollection::Add(const Notification& notification,
+ Profile* profile) {
+ // Swap in the logging proxy for the purpose of logging calls that
+ // would be made into javascript, then pass this down to the
+ // balloon collection.
+ Notification test_notification(notification.origin_url(),
+ notification.content_url(),
+ notification.display_source(),
+ string16(), /* replace_id */
+ log_proxy_.get());
+ BalloonCollectionImpl::Add(test_notification, profile);
+}
+
+bool MockBalloonCollection::Remove(const Notification& notification) {
+ Notification test_notification(notification.origin_url(),
+ notification.content_url(),
+ notification.display_source(),
+ string16(), /* replace_id */
+ log_proxy_.get());
+ return BalloonCollectionImpl::Remove(test_notification);
+}
+
+Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification,
+ Profile* profile) {
+ // Start with a normal balloon but mock out the view.
+ Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile);
+ balloon->set_view(new MockBalloonView(balloon));
+ balloons_.push_back(balloon);
+ return balloon;
+}
+
+void MockBalloonCollection::OnBalloonClosed(Balloon* source) {
+ std::deque<Balloon*>::iterator it;
+ for (it = balloons_.begin(); it != balloons_.end(); ++it) {
+ if (*it == source) {
+ balloons_.erase(it);
+ BalloonCollectionImpl::OnBalloonClosed(source);
+ break;
+ }
+ }
+}
+
+int MockBalloonCollection::UppermostVerticalPosition() {
+ int min = 0;
+ std::deque<Balloon*>::iterator iter;
+ for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) {
+ int pos = (*iter)->GetPosition().y();
+ if (iter == balloons_.begin() || pos < min)
+ min = pos;
+ }
+ return min;
+}
+
+DesktopNotificationsTest::DesktopNotificationsTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_) {
+}
+
+DesktopNotificationsTest::~DesktopNotificationsTest() {
+}
+
+void DesktopNotificationsTest::SetUp() {
+ profile_.reset(new TestingProfile());
+ balloon_collection_ = new MockBalloonCollection();
+ ui_manager_.reset(new NotificationUIManager());
+ ui_manager_->Initialize(balloon_collection_);
+ balloon_collection_->set_space_change_listener(ui_manager_.get());
+ service_.reset(new DesktopNotificationService(profile(), ui_manager_.get()));
+ log_output_.clear();
+}
+
+void DesktopNotificationsTest::TearDown() {
+ service_.reset(NULL);
+ profile_.reset(NULL);
+ ui_manager_.reset(NULL);
+}
+
+ViewHostMsg_ShowNotification_Params
+DesktopNotificationsTest::StandardTestNotification() {
+ ViewHostMsg_ShowNotification_Params params;
+ params.notification_id = 0;
+ params.origin = GURL("http://www.google.com");
+ params.is_html = false;
+ params.icon_url = GURL("/icon.png");
+ params.title = ASCIIToUTF16("Title");
+ params.body = ASCIIToUTF16("Text");
+ params.direction = WebKit::WebTextDirectionDefault;
+ return params;
+}
+
+TEST_F(DesktopNotificationsTest, TestShow) {
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ params.notification_id = 1;
+
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ MessageLoopForUI::current()->RunAllPending();
+ EXPECT_EQ(1, balloon_collection_->count());
+
+ ViewHostMsg_ShowNotification_Params params2;
+ params2.origin = GURL("http://www.google.com");
+ params2.is_html = true;
+ params2.contents_url = GURL("http://www.google.com/notification.html");
+ params2.notification_id = 2;
+
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params2, 0, 0, DesktopNotificationService::PageNotification));
+ MessageLoopForUI::current()->RunAllPending();
+ EXPECT_EQ(2, balloon_collection_->count());
+
+ EXPECT_EQ("notification displayed\n"
+ "notification displayed\n",
+ log_output_);
+}
+
+TEST_F(DesktopNotificationsTest, TestClose) {
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ params.notification_id = 1;
+
+ // Request a notification; should open a balloon.
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ MessageLoopForUI::current()->RunAllPending();
+ EXPECT_EQ(1, balloon_collection_->count());
+
+ // Close all the open balloons.
+ while (balloon_collection_->count() > 0) {
+ (*(balloon_collection_->GetActiveBalloons().begin()))->OnClose(true);
+ }
+
+ EXPECT_EQ("notification displayed\n"
+ "notification closed by user\n",
+ log_output_);
+}
+
+TEST_F(DesktopNotificationsTest, TestCancel) {
+ int process_id = 0;
+ int route_id = 0;
+ int notification_id = 1;
+
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ params.notification_id = notification_id;
+
+ // Request a notification; should open a balloon.
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, process_id, route_id,
+ DesktopNotificationService::PageNotification));
+ MessageLoopForUI::current()->RunAllPending();
+ EXPECT_EQ(1, balloon_collection_->count());
+
+ // Cancel the same notification
+ service_->CancelDesktopNotification(process_id,
+ route_id,
+ notification_id);
+ MessageLoopForUI::current()->RunAllPending();
+ // Verify that the balloon collection is now empty.
+ EXPECT_EQ(0, balloon_collection_->count());
+
+ EXPECT_EQ("notification displayed\n"
+ "notification closed by script\n",
+ log_output_);
+}
+
+#if defined(OS_WIN) || defined(TOOLKIT_VIEWS)
+TEST_F(DesktopNotificationsTest, TestPositioning) {
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ std::string expected_log;
+ // Create some toasts. After each but the first, make sure there
+ // is a minimum separation between the toasts.
+ int last_top = 0;
+ for (int id = 0; id <= 3; ++id) {
+ params.notification_id = id;
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ expected_log.append("notification displayed\n");
+ int top = balloon_collection_->UppermostVerticalPosition();
+ if (id > 0)
+ EXPECT_LE(top, last_top - balloon_collection_->MinHeight());
+ last_top = top;
+ }
+
+ EXPECT_EQ(expected_log, log_output_);
+}
+
+TEST_F(DesktopNotificationsTest, TestVariableSize) {
+ ViewHostMsg_ShowNotification_Params params;
+ params.origin = GURL("http://long.google.com");
+ params.is_html = false;
+ params.icon_url = GURL("/icon.png");
+ params.title = ASCIIToUTF16("Really Really Really Really Really Really "
+ "Really Really Really Really Really Really "
+ "Really Really Really Really Really Really "
+ "Really Long Title"),
+ params.body = ASCIIToUTF16("Text");
+ params.notification_id = 0;
+
+ std::string expected_log;
+ // Create some toasts. After each but the first, make sure there
+ // is a minimum separation between the toasts.
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ expected_log.append("notification displayed\n");
+
+ params.origin = GURL("http://short.google.com");
+ params.title = ASCIIToUTF16("Short title");
+ params.notification_id = 1;
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ expected_log.append("notification displayed\n");
+
+ std::deque<Balloon*>& balloons = balloon_collection_->balloons();
+ std::deque<Balloon*>::iterator iter;
+ for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
+ if ((*iter)->notification().origin_url().host() == "long.google.com") {
+ EXPECT_GE((*iter)->GetViewSize().height(),
+ balloon_collection_->MinHeight());
+ EXPECT_LE((*iter)->GetViewSize().height(),
+ balloon_collection_->MaxHeight());
+ } else {
+ EXPECT_EQ((*iter)->GetViewSize().height(),
+ balloon_collection_->MinHeight());
+ }
+ }
+ EXPECT_EQ(expected_log, log_output_);
+}
+#endif
+
+TEST_F(DesktopNotificationsTest, TestQueueing) {
+ int process_id = 0;
+ int route_id = 0;
+
+ // Request lots of identical notifications.
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ const int kLotsOfToasts = 20;
+ for (int id = 1; id <= kLotsOfToasts; ++id) {
+ params.notification_id = id;
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, process_id, route_id,
+ DesktopNotificationService::PageNotification));
+ }
+ MessageLoopForUI::current()->RunAllPending();
+
+ // Build up an expected log of what should be happening.
+ std::string expected_log;
+ for (int i = 0; i < balloon_collection_->max_balloon_count(); ++i) {
+ expected_log.append("notification displayed\n");
+ }
+
+ // The max number that our balloon collection can hold should be
+ // shown.
+ EXPECT_EQ(balloon_collection_->max_balloon_count(),
+ balloon_collection_->count());
+ EXPECT_EQ(expected_log, log_output_);
+
+ // Cancel the notifications from the start; the balloon space should
+ // remain full.
+ int id;
+ for (id = 1;
+ id <= kLotsOfToasts - balloon_collection_->max_balloon_count();
+ ++id) {
+ service_->CancelDesktopNotification(process_id, route_id, id);
+ MessageLoopForUI::current()->RunAllPending();
+ expected_log.append("notification closed by script\n");
+ expected_log.append("notification displayed\n");
+ EXPECT_EQ(balloon_collection_->max_balloon_count(),
+ balloon_collection_->count());
+ EXPECT_EQ(expected_log, log_output_);
+ }
+
+ // Now cancel the rest. It should empty the balloon space.
+ for (; id <= kLotsOfToasts; ++id) {
+ service_->CancelDesktopNotification(process_id, route_id, id);
+ expected_log.append("notification closed by script\n");
+ MessageLoopForUI::current()->RunAllPending();
+ EXPECT_EQ(expected_log, log_output_);
+ }
+
+ // Verify that the balloon collection is now empty.
+ EXPECT_EQ(0, balloon_collection_->count());
+}
+
+TEST_F(DesktopNotificationsTest, TestEarlyDestruction) {
+ // Create some toasts and then prematurely delete the notification service,
+ // just to make sure nothing crashes/leaks.
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ for (int id = 0; id <= 3; ++id) {
+ params.notification_id = id;
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ }
+ service_.reset(NULL);
+}
+
+TEST_F(DesktopNotificationsTest, TestUserInputEscaping) {
+ // Create a test script with some HTML; assert that it doesn't get into the
+ // data:// URL that's produced for the balloon.
+ ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
+ params.title = ASCIIToUTF16("<script>window.alert('uh oh');</script>");
+ params.body = ASCIIToUTF16("<i>this text is in italics</i>");
+ params.notification_id = 1;
+ EXPECT_TRUE(service_->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+
+ MessageLoopForUI::current()->RunAllPending();
+ EXPECT_EQ(1, balloon_collection_->count());
+ Balloon* balloon = (*balloon_collection_->balloons().begin());
+ GURL data_url = balloon->notification().content_url();
+ EXPECT_EQ(std::string::npos, data_url.spec().find("<script>"));
+ EXPECT_EQ(std::string::npos, data_url.spec().find("<i>"));
+ // URL-encoded versions of tags should also not be found.
+ EXPECT_EQ(std::string::npos, data_url.spec().find("%3cscript%3e"));
+ EXPECT_EQ(std::string::npos, data_url.spec().find("%3ci%3e"));
+}
diff --git a/chrome/browser/notifications/desktop_notifications_unittest.h b/chrome/browser/notifications/desktop_notifications_unittest.h
new file mode 100644
index 0000000..56a2a6b
--- /dev/null
+++ b/chrome/browser/notifications/desktop_notifications_unittest.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATIONS_UNITTEST_H_
+#define CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATIONS_UNITTEST_H_
+
+#include <deque>
+#include <string>
+
+#include "base/message_loop.h"
+#include "chrome/browser/notifications/balloon_collection_impl.h"
+#include "chrome/browser/notifications/desktop_notification_service.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/notifications/notification_test_util.h"
+#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chrome/browser/notifications/notifications_prefs_cache.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/test/testing_profile.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class DesktopNotificationsTest;
+typedef LoggingNotificationProxyBase<DesktopNotificationsTest>
+ LoggingNotificationProxy;
+
+// Test version of the balloon collection which counts the number
+// of notifications that are added to it.
+class MockBalloonCollection : public BalloonCollectionImpl {
+ public:
+ MockBalloonCollection() :
+ log_proxy_(new LoggingNotificationProxy()) {}
+
+ // Our mock collection has an area large enough for a fixed number
+ // of balloons.
+ static const int kMockBalloonSpace;
+ int max_balloon_count() const { return kMockBalloonSpace; }
+
+ // BalloonCollectionImpl overrides
+ virtual void Add(const Notification& notification,
+ Profile* profile);
+ virtual bool Remove(const Notification& notification);
+ virtual bool HasSpace() const { return count() < kMockBalloonSpace; }
+ virtual Balloon* MakeBalloon(const Notification& notification,
+ Profile* profile);
+ virtual void DisplayChanged() {}
+ virtual void OnBalloonClosed(Balloon* source);
+ virtual const BalloonCollection::Balloons& GetActiveBalloons() {
+ return balloons_;
+ }
+
+ // Number of balloons being shown.
+ std::deque<Balloon*>& balloons() { return balloons_; }
+ int count() const { return balloons_.size(); }
+
+ // Returns the highest y-coordinate of all the balloons in the collection.
+ int UppermostVerticalPosition();
+
+ // Returns the height bounds of a balloon.
+ int MinHeight() { return Layout::min_balloon_height(); }
+ int MaxHeight() { return Layout::max_balloon_height(); }
+
+ private:
+ std::deque<Balloon*> balloons_;
+ scoped_refptr<LoggingNotificationProxy> log_proxy_;
+};
+
+class DesktopNotificationsTest : public testing::Test {
+ public:
+ DesktopNotificationsTest();
+ ~DesktopNotificationsTest();
+
+ static void log(const std::string& message) {
+ log_output_.append(message);
+ }
+
+ Profile* profile() { return profile_.get(); }
+
+ protected:
+ // testing::Test overrides
+ virtual void SetUp();
+ virtual void TearDown();
+
+ void AllowOrigin(const GURL& origin) {
+ service_->GrantPermission(origin);
+ }
+
+ void DenyOrigin(const GURL& origin) {
+ service_->DenyPermission(origin);
+ }
+
+ int HasPermission(const GURL& origin) {
+ return service_->prefs_cache()->HasPermission(origin);
+ }
+
+ // Constructs a notification parameter structure for use in tests.
+ ViewHostMsg_ShowNotification_Params StandardTestNotification();
+
+ // Create a message loop to allow notifications code to post tasks,
+ // and a thread so that notifications code runs on the expected thread.
+ MessageLoopForUI message_loop_;
+ ChromeThread ui_thread_;
+
+ // Test profile.
+ scoped_ptr<TestingProfile> profile_;
+
+ // Mock balloon collection -- owned by the NotificationUIManager
+ MockBalloonCollection* balloon_collection_;
+
+ // Real UI manager.
+ scoped_ptr<NotificationUIManager> ui_manager_;
+
+ // Real DesktopNotificationService
+ scoped_ptr<DesktopNotificationService> service_;
+
+ // Contains the cumulative output of the unit test.
+ static std::string log_output_;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATIONS_UNITTEST_H_
diff --git a/chrome/browser/notifications/notification.h b/chrome/browser/notifications/notification.h
new file mode 100644
index 0000000..bdad291
--- /dev/null
+++ b/chrome/browser/notifications/notification.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_H_
+
+#include "base/basictypes.h"
+#include "chrome/browser/notifications/notification_object_proxy.h"
+#include "googleurl/src/gurl.h"
+
+class NotificationDelegate;
+
+// Representation of an notification to be shown to the user. All
+// notifications at this level are HTML, although they may be
+// data: URLs representing simple text+icon notifications.
+class Notification {
+ public:
+ // TODO: http://crbug.com/43899 Convert this class to string16.
+ Notification(const GURL& origin_url, const GURL& content_url,
+ const std::wstring& display_source,
+ const string16& replace_id,
+ NotificationDelegate* delegate)
+ : origin_url_(origin_url),
+ content_url_(content_url),
+ display_source_(display_source),
+ replace_id_(replace_id),
+ delegate_(delegate) {
+ }
+
+ Notification(const Notification& notification)
+ : origin_url_(notification.origin_url()),
+ content_url_(notification.content_url()),
+ display_source_(notification.display_source()),
+ replace_id_(notification.replace_id()),
+ delegate_(notification.delegate()) {
+ }
+
+ void operator=(const Notification& notification) {
+ origin_url_ = notification.origin_url();
+ content_url_ = notification.content_url();
+ display_source_ = notification.display_source();
+ replace_id_ = notification.replace_id();
+ delegate_ = notification.delegate();
+ }
+
+ // The URL (may be data:) containing the contents for the notification.
+ const GURL& content_url() const { return content_url_; }
+
+ // The origin URL of the script which requested the notification.
+ const GURL& origin_url() const { return origin_url_; }
+
+ // A display string for the source of the notification.
+ const std::wstring& display_source() const { return display_source_; }
+
+ const string16& replace_id() const { return replace_id_; }
+
+ void Display() const { delegate()->Display(); }
+ void Error() const { delegate()->Error(); }
+ void Close(bool by_user) const { delegate()->Close(by_user); }
+
+ bool IsSame(const Notification& other) const {
+ return delegate()->id() == other.delegate()->id();
+ }
+
+ private:
+ NotificationDelegate* delegate() const { return delegate_.get(); }
+
+ // The Origin of the page/worker which created this notification.
+ GURL origin_url_;
+
+ // The URL of the HTML content of the toast (may be a data: URL for simple
+ // string-based notifications).
+ GURL content_url_;
+
+ // The display string for the source of the notification. Could be
+ // the same as origin_url_, or the name of an extension.
+ std::wstring display_source_;
+
+ // The replace ID for the notification.
+ string16 replace_id_;
+
+ // A proxy object that allows access back to the JavaScript object that
+ // represents the notification, for firing events.
+ scoped_refptr<NotificationDelegate> delegate_;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_H_
diff --git a/chrome/browser/notifications/notification_delegate.h b/chrome/browser/notifications/notification_delegate.h
new file mode 100644
index 0000000..6d69335
--- /dev/null
+++ b/chrome/browser/notifications/notification_delegate.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_DELEGATE_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_DELEGATE_H_
+
+#include <string>
+
+#include "base/ref_counted.h"
+
+// Delegate for a notification. This class has two role, to implement
+// callback methods for notification, and provides an identify of
+// the associated notification.
+class NotificationDelegate
+ : public base::RefCountedThreadSafe<NotificationDelegate> {
+ public:
+
+ // To be called when the desktop notification is actually shown.
+ virtual void Display() = 0;
+
+ // To be called when the desktop notification cannot be shown due to an
+ // error.
+ virtual void Error() = 0;
+
+ // To be called when the desktop notification is closed. If closed by a
+ // user explicitly (as opposed to timeout/script), |by_user| should be true.
+ virtual void Close(bool by_user) = 0;
+
+ // Returns unique id of the notification.
+ virtual std::string id() const = 0;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_DELEGATE_H_
diff --git a/chrome/browser/notifications/notification_exceptions_table_model.cc b/chrome/browser/notifications/notification_exceptions_table_model.cc
new file mode 100644
index 0000000..fdb08f4
--- /dev/null
+++ b/chrome/browser/notifications/notification_exceptions_table_model.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/notification_exceptions_table_model.h"
+
+#include "app/l10n_util.h"
+#include "app/l10n_util_collator.h"
+#include "app/table_model_observer.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/content_settings_helper.h"
+#include "chrome/common/url_constants.h"
+#include "grit/generated_resources.h"
+
+NotificationExceptionsTableModel::NotificationExceptionsTableModel(
+ DesktopNotificationService* service)
+ : service_(service),
+ observer_(NULL) {
+ std::vector<GURL> allowed(service_->GetAllowedOrigins());
+ std::vector<GURL> blocked(service_->GetBlockedOrigins());
+ entries_.reserve(allowed.size() + blocked.size());
+ for (size_t i = 0; i < allowed.size(); ++i)
+ entries_.push_back(Entry(allowed[i], CONTENT_SETTING_ALLOW));
+ for (size_t i = 0; i < blocked.size(); ++i)
+ entries_.push_back(Entry(blocked[i], CONTENT_SETTING_BLOCK));
+ sort(entries_.begin(), entries_.end());
+}
+
+bool NotificationExceptionsTableModel::CanRemoveRows(
+ const Rows& rows) const {
+ return !rows.empty();
+}
+
+void NotificationExceptionsTableModel::RemoveRows(const Rows& rows) {
+ // This is O(n^2) in rows.size(). Since n is small, that's ok.
+ for (Rows::const_reverse_iterator i(rows.rbegin()); i != rows.rend(); ++i) {
+ size_t row = *i;
+ Entry* entry = &entries_[row];
+ if (entry->setting == CONTENT_SETTING_ALLOW) {
+ service_->ResetAllowedOrigin(entry->origin);
+ } else {
+ DCHECK_EQ(entry->setting, CONTENT_SETTING_BLOCK);
+ service_->ResetBlockedOrigin(entry->origin);
+ }
+ entries_.erase(entries_.begin() + row); // Note: |entry| is now garbage.
+ if (observer_)
+ observer_->OnItemsRemoved(row, 1);
+ }
+}
+
+void NotificationExceptionsTableModel::RemoveAll() {
+ int old_row_count = RowCount();
+ entries_.clear();
+ service_->ResetAllOrigins();
+ if (observer_)
+ observer_->OnItemsRemoved(0, old_row_count);
+}
+
+int NotificationExceptionsTableModel::RowCount() {
+ return static_cast<int>(entries_.size());
+}
+
+std::wstring NotificationExceptionsTableModel::GetText(int row,
+ int column_id) {
+ const Entry& entry = entries_[row];
+ if (column_id == IDS_EXCEPTIONS_HOSTNAME_HEADER) {
+ return content_settings_helper::OriginToWString(entry.origin);
+ }
+
+ if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) {
+ switch (entry.setting) {
+ case CONTENT_SETTING_ALLOW:
+ return l10n_util::GetString(IDS_EXCEPTIONS_ALLOW_BUTTON);
+ case CONTENT_SETTING_BLOCK:
+ return l10n_util::GetString(IDS_EXCEPTIONS_BLOCK_BUTTON);
+ default:
+ break;
+ }
+ }
+
+ NOTREACHED();
+ return std::wstring();
+}
+
+void NotificationExceptionsTableModel::SetObserver(
+ TableModelObserver* observer) {
+ observer_ = observer;
+}
+
+NotificationExceptionsTableModel::Entry::Entry(
+ const GURL& in_origin,
+ ContentSetting in_setting)
+ : origin(in_origin),
+ setting(in_setting) {
+}
+
+bool NotificationExceptionsTableModel::Entry::operator<(
+ const NotificationExceptionsTableModel::Entry& b) const {
+ DCHECK_NE(origin, b.origin);
+ return origin < b.origin;
+}
diff --git a/chrome/browser/notifications/notification_exceptions_table_model.h b/chrome/browser/notifications/notification_exceptions_table_model.h
new file mode 100644
index 0000000..01137f7
--- /dev/null
+++ b/chrome/browser/notifications/notification_exceptions_table_model.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_EXCEPTIONS_TABLE_MODEL_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_EXCEPTIONS_TABLE_MODEL_H_
+
+#include <set>
+#include <vector>
+
+#include "chrome/browser/notifications/desktop_notification_service.h"
+#include "chrome/browser/remove_rows_table_model.h"
+#include "chrome/common/content_settings.h"
+#include "chrome/common/content_settings_types.h"
+
+class NotificationExceptionsTableModel : public RemoveRowsTableModel {
+ public:
+ explicit NotificationExceptionsTableModel(
+ DesktopNotificationService* service);
+
+ // RemoveRowsTableModel overrides:
+ virtual bool CanRemoveRows(const Rows& rows) const;
+ virtual void RemoveRows(const Rows& rows);
+ virtual void RemoveAll();
+
+ // TableModel overrides:
+ virtual int RowCount();
+ virtual std::wstring GetText(int row, int column_id);
+ virtual void SetObserver(TableModelObserver* observer);
+
+ private:
+ struct Entry {
+ Entry(const GURL& origin, ContentSetting setting);
+ bool operator<(const Entry& b) const;
+
+ GURL origin;
+ ContentSetting setting;
+ };
+
+ DesktopNotificationService* service_;
+
+ typedef std::vector<Entry> EntriesVector;
+ EntriesVector entries_;
+
+ TableModelObserver* observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationExceptionsTableModel);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_EXCEPTIONS_TABLE_MODEL_H_
+
diff --git a/chrome/browser/notifications/notification_exceptions_table_model_unittest.cc b/chrome/browser/notifications/notification_exceptions_table_model_unittest.cc
new file mode 100644
index 0000000..6919b0b
--- /dev/null
+++ b/chrome/browser/notifications/notification_exceptions_table_model_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/notification_exceptions_table_model.h"
+
+#include "app/l10n_util.h"
+#include "chrome/browser/renderer_host/test/test_render_view_host.h"
+#include "chrome/test/testing_profile.h"
+#include "grit/generated_resources.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class NotificationExceptionsTableModelTest : public RenderViewHostTestHarness {
+ public:
+ NotificationExceptionsTableModelTest()
+ : ui_thread_(ChromeThread::UI, MessageLoop::current()) {
+ }
+
+ virtual ~NotificationExceptionsTableModelTest() {
+ }
+
+ virtual void SetUp() {
+ RenderViewHostTestHarness::SetUp();
+ service_ = profile()->GetDesktopNotificationService();
+ ResetModel();
+ }
+
+ virtual void TearDown() {
+ model_.reset(NULL);
+ RenderViewHostTestHarness::TearDown();
+ }
+
+ virtual void ResetModel() {
+ model_.reset(new NotificationExceptionsTableModel(service_));
+ }
+
+ virtual void FillData() {
+ service_->GrantPermission(GURL("http://e-allowed2.com"));
+ service_->GrantPermission(GURL("http://allowed.com"));
+
+ service_->DenyPermission(GURL("http://denied2.com"));
+ service_->DenyPermission(GURL("http://denied.com"));
+ service_->DenyPermission(GURL("http://f-denied3.com"));
+
+ ResetModel();
+ }
+
+ protected:
+ ChromeThread ui_thread_;
+ scoped_ptr<NotificationExceptionsTableModel> model_;
+ DesktopNotificationService* service_;
+};
+
+TEST_F(NotificationExceptionsTableModelTest, CanCreate) {
+ EXPECT_EQ(0, model_->RowCount());
+}
+
+TEST_F(NotificationExceptionsTableModelTest, RemoveAll) {
+ FillData();
+ EXPECT_EQ(2u, service_->GetAllowedOrigins().size());
+ EXPECT_EQ(3u, service_->GetBlockedOrigins().size());
+ EXPECT_EQ(5, model_->RowCount());
+
+ model_->RemoveAll();
+ EXPECT_EQ(0, model_->RowCount());
+
+ EXPECT_EQ(0u, service_->GetAllowedOrigins().size());
+ EXPECT_EQ(0u, service_->GetBlockedOrigins().size());
+}
+
+TEST_F(NotificationExceptionsTableModelTest, AlphabeticalOrder) {
+ FillData();
+ EXPECT_EQ(5, model_->RowCount());
+
+ EXPECT_EQ(L"allowed.com",
+ model_->GetText(0, IDS_EXCEPTIONS_HOSTNAME_HEADER));
+ EXPECT_EQ(l10n_util::GetString(IDS_EXCEPTIONS_ALLOW_BUTTON),
+ model_->GetText(0, IDS_EXCEPTIONS_ACTION_HEADER));
+
+ EXPECT_EQ(L"denied.com",
+ model_->GetText(1, IDS_EXCEPTIONS_HOSTNAME_HEADER));
+ EXPECT_EQ(l10n_util::GetString(IDS_EXCEPTIONS_BLOCK_BUTTON),
+ model_->GetText(1, IDS_EXCEPTIONS_ACTION_HEADER));
+
+ EXPECT_EQ(L"denied2.com",
+ model_->GetText(2, IDS_EXCEPTIONS_HOSTNAME_HEADER));
+ EXPECT_EQ(l10n_util::GetString(IDS_EXCEPTIONS_BLOCK_BUTTON),
+ model_->GetText(2, IDS_EXCEPTIONS_ACTION_HEADER));
+
+ EXPECT_EQ(L"e-allowed2.com",
+ model_->GetText(3, IDS_EXCEPTIONS_HOSTNAME_HEADER));
+ EXPECT_EQ(l10n_util::GetString(IDS_EXCEPTIONS_ALLOW_BUTTON),
+ model_->GetText(3, IDS_EXCEPTIONS_ACTION_HEADER));
+
+ EXPECT_EQ(L"f-denied3.com",
+ model_->GetText(4, IDS_EXCEPTIONS_HOSTNAME_HEADER));
+ EXPECT_EQ(l10n_util::GetString(IDS_EXCEPTIONS_BLOCK_BUTTON),
+ model_->GetText(4, IDS_EXCEPTIONS_ACTION_HEADER));
+}
+
+TEST_F(NotificationExceptionsTableModelTest, RemoveRows) {
+ FillData();
+ EXPECT_EQ(5, model_->RowCount());
+
+ {
+ RemoveRowsTableModel::Rows rows;
+ rows.insert(0); // allowed.com
+ rows.insert(3); // e-allowed2.com
+ model_->RemoveRows(rows);
+ }
+ EXPECT_EQ(3, model_->RowCount());
+ EXPECT_EQ(0u, service_->GetAllowedOrigins().size());
+ EXPECT_EQ(3u, service_->GetBlockedOrigins().size());
+
+ {
+ RemoveRowsTableModel::Rows rows;
+ rows.insert(0);
+ rows.insert(1);
+ rows.insert(2);
+ model_->RemoveRows(rows);
+ }
+ EXPECT_EQ(0, model_->RowCount());
+ EXPECT_EQ(0u, service_->GetAllowedOrigins().size());
+ EXPECT_EQ(0u, service_->GetBlockedOrigins().size());
+}
diff --git a/chrome/browser/notifications/notification_object_proxy.cc b/chrome/browser/notifications/notification_object_proxy.cc
new file mode 100644
index 0000000..76363e5
--- /dev/null
+++ b/chrome/browser/notifications/notification_object_proxy.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2009 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 "chrome/browser/notifications/notification_object_proxy.h"
+
+#include "base/message_loop.h"
+#include "base/string16.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/common/render_messages.h"
+
+NotificationObjectProxy::NotificationObjectProxy(int process_id, int route_id,
+ int notification_id, bool worker)
+ : process_id_(process_id),
+ route_id_(route_id),
+ notification_id_(notification_id),
+ worker_(worker) {
+}
+
+void NotificationObjectProxy::Display() {
+ if (worker_) {
+ // TODO(johnnyg): http://crbug.com/23065 Worker support coming soon.
+ NOTREACHED();
+ } else {
+ DeliverMessage(new ViewMsg_PostDisplayToNotificationObject(
+ route_id_, notification_id_));
+ }
+}
+
+void NotificationObjectProxy::Error() {
+ if (worker_) {
+ // TODO(johnnyg): http://crbug.com/23065 Worker support coming soon.
+ NOTREACHED();
+ } else {
+ DeliverMessage(new ViewMsg_PostErrorToNotificationObject(
+ route_id_, notification_id_, string16()));
+ }
+}
+
+void NotificationObjectProxy::Close(bool by_user) {
+ if (worker_) {
+ // TODO(johnnyg): http://crbug.com/23065 Worker support coming soon.
+ NOTREACHED();
+ } else {
+ DeliverMessage(new ViewMsg_PostCloseToNotificationObject(
+ route_id_, notification_id_, by_user));
+ }
+}
+
+std::string NotificationObjectProxy::id() const {
+ return StringPrintf("%d:%d:%d:%d", process_id_, route_id_,
+ notification_id_, worker_);
+}
+
+
+void NotificationObjectProxy::DeliverMessage(IPC::Message* message) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this, &NotificationObjectProxy::Send, message));
+}
+
+// Deferred method which runs on the IO thread and sends a message to the
+// proxied notification, routing it through the correct host in the browser.
+void NotificationObjectProxy::Send(IPC::Message* message) {
+ // Take ownership of the message; ownership will pass to a host if possible.
+ scoped_ptr<IPC::Message> owned_message(message);
+
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+ RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_);
+ if (host) {
+ // Pass ownership to the host.
+ host->Send(owned_message.release());
+ }
+}
diff --git a/chrome/browser/notifications/notification_object_proxy.h b/chrome/browser/notifications/notification_object_proxy.h
new file mode 100644
index 0000000..a956b3a
--- /dev/null
+++ b/chrome/browser/notifications/notification_object_proxy.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2009 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_OBJECT_PROXY_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_OBJECT_PROXY_H_
+
+#include <string>
+
+#include "chrome/browser/notifications/notification_delegate.h"
+
+class MessageLoop;
+namespace IPC {
+class Message;
+}
+
+// A NotificationObjectProxy stands in for the JavaScript Notification object
+// which corresponds to a notification toast on the desktop. It can be signaled
+// when various events occur regarding the desktop notification, and the
+// attached JS listeners will be invoked in the renderer or worker process.
+class NotificationObjectProxy
+ : public NotificationDelegate {
+ public:
+ // Creates a Proxy object with the necessary callback information.
+ NotificationObjectProxy(int process_id, int route_id,
+ int notification_id, bool worker);
+
+ // NotificationDelegate implementation.
+ virtual void Display();
+ virtual void Error();
+ virtual void Close(bool by_user);
+ virtual std::string id() const;
+
+ protected:
+ friend class base::RefCountedThreadSafe<NotificationObjectProxy>;
+
+ virtual ~NotificationObjectProxy() {}
+
+ private:
+ // Called on UI thread to schedule a message for sending.
+ void DeliverMessage(IPC::Message* message);
+
+ // Called via Task on IO thread to actually send a message to a notification.
+ void Send(IPC::Message* message);
+
+ // Callback information to find the JS Notification object where it lives.
+ int process_id_;
+ int route_id_;
+ int notification_id_;
+ bool worker_;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_OBJECT_PROXY_H_
diff --git a/chrome/browser/notifications/notification_options_menu_model.cc b/chrome/browser/notifications/notification_options_menu_model.cc
new file mode 100644
index 0000000..66379fa
--- /dev/null
+++ b/chrome/browser/notifications/notification_options_menu_model.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/notification_options_menu_model.h"
+
+#include "app/l10n_util.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/notifications/desktop_notification_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/content_settings_types.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/url_constants.h"
+#include "grit/generated_resources.h"
+
+// Menu commands
+const int kRevokePermissionCommand = 0;
+const int kDisableExtensionCommand = 1;
+const int kOpenContentSettingsCommand = 2;
+
+NotificationOptionsMenuModel::NotificationOptionsMenuModel(Balloon* balloon)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)),
+ balloon_(balloon) {
+
+ const Notification& notification = balloon->notification();
+ const GURL& origin = notification.origin_url();
+
+ if (origin.SchemeIs(chrome::kExtensionScheme)) {
+ const string16 disable_label = l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_DISABLE);
+ AddItem(kDisableExtensionCommand, disable_label);
+ } else {
+ const string16 disable_label = l10n_util::GetStringFUTF16(
+ IDS_NOTIFICATION_BALLOON_REVOKE_MESSAGE,
+ WideToUTF16(notification.display_source()));
+ AddItem(kRevokePermissionCommand, disable_label);
+ }
+
+ const string16 settings_label = l10n_util::GetStringUTF16(
+ IDS_NOTIFICATIONS_SETTINGS_BUTTON);
+ AddItem(kOpenContentSettingsCommand, settings_label);
+}
+
+NotificationOptionsMenuModel::~NotificationOptionsMenuModel() {
+}
+
+bool NotificationOptionsMenuModel::IsCommandIdChecked(int /* command_id */)
+ const {
+ // Nothing in the menu is checked.
+ return false;
+}
+
+bool NotificationOptionsMenuModel::IsCommandIdEnabled(int /* command_id */)
+ const {
+ // All the menu options are always enabled.
+ return true;
+}
+
+bool NotificationOptionsMenuModel::GetAcceleratorForCommandId(
+ int /* command_id */, menus::Accelerator* /* accelerator */) {
+ // Currently no accelerators.
+ return false;
+}
+
+void NotificationOptionsMenuModel::ExecuteCommand(int command_id) {
+ DesktopNotificationService* service =
+ balloon_->profile()->GetDesktopNotificationService();
+ ExtensionsService* ext_service =
+ balloon_->profile()->GetExtensionsService();
+ const GURL& origin = balloon_->notification().origin_url();
+ switch (command_id) {
+ case kRevokePermissionCommand:
+ service->DenyPermission(origin);
+ break;
+ case kDisableExtensionCommand: {
+ Extension* extension = ext_service->GetExtensionByURL(origin);
+ if (extension)
+ ext_service->DisableExtension(extension->id());
+ break;
+ }
+ case kOpenContentSettingsCommand: {
+ Browser* browser = BrowserList::GetLastActive();
+ if (browser)
+ static_cast<TabContentsDelegate*>(browser)->ShowContentSettingsWindow(
+ CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+}
diff --git a/chrome/browser/notifications/notification_options_menu_model.h b/chrome/browser/notifications/notification_options_menu_model.h
new file mode 100644
index 0000000..751c799
--- /dev/null
+++ b/chrome/browser/notifications/notification_options_menu_model.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_OPTIONS_MENU_MODEL_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_OPTIONS_MENU_MODEL_H_
+
+#include "app/menus/simple_menu_model.h"
+#include "chrome/browser/notifications/balloon.h"
+
+class NotificationOptionsMenuModel : public menus::SimpleMenuModel,
+ public menus::SimpleMenuModel::Delegate {
+ public:
+ explicit NotificationOptionsMenuModel(Balloon* balloon);
+ virtual ~NotificationOptionsMenuModel();
+
+ // Overridden from menus::SimpleMenuModel::Delegate:
+ virtual bool IsCommandIdChecked(int command_id) const;
+ virtual bool IsCommandIdEnabled(int command_id) const;
+ virtual bool GetAcceleratorForCommandId(int command_id,
+ menus::Accelerator* accelerator);
+ virtual void ExecuteCommand(int command_id);
+
+ private:
+ Balloon* balloon_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationOptionsMenuModel);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_OPTIONS_MENU_MODEL_H_
diff --git a/chrome/browser/notifications/notification_test_util.h b/chrome/browser/notifications/notification_test_util.h
new file mode 100644
index 0000000..d3ad7ee
--- /dev/null
+++ b/chrome/browser/notifications/notification_test_util.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_TEST_UTIL_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_TEST_UTIL_H_
+
+#include "chrome/browser/notifications/notification_object_proxy.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "gfx/size.h"
+
+// Mock implementation of Javascript object proxy which logs events that
+// would have been fired on it. |Logger| class must static "log()" method.
+template<class Logger>
+class LoggingNotificationProxyBase : public NotificationObjectProxy {
+ public:
+ LoggingNotificationProxyBase() :
+ NotificationObjectProxy(0, 0, 0, false) {}
+
+ // NotificationObjectProxy override
+ virtual void Display() {
+ Logger::log("notification displayed\n");
+ }
+ virtual void Error() {
+ Logger::log("notification error\n");
+ }
+ virtual void Close(bool by_user) {
+ if (by_user)
+ Logger::log("notification closed by user\n");
+ else
+ Logger::log("notification closed by script\n");
+ }
+};
+
+// Test version of a balloon view which doesn't do anything
+// viewable, but does know how to close itself the same as a regular
+// BalloonView.
+class MockBalloonView : public BalloonView {
+ public:
+ explicit MockBalloonView(Balloon * balloon) :
+ balloon_(balloon) {}
+ void Show(Balloon* balloon) {}
+ void Update() {}
+ void RepositionToBalloon() {}
+ void Close(bool by_user) { balloon_->OnClose(by_user); }
+ gfx::Size GetSize() const { return balloon_->content_size(); }
+ BalloonHost* GetHost() const { return NULL; }
+
+ private:
+ // Non-owned pointer.
+ Balloon* balloon_;
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_TEST_UTIL_H_
diff --git a/chrome/browser/notifications/notification_ui_manager.cc b/chrome/browser/notifications/notification_ui_manager.cc
new file mode 100644
index 0000000..42b9c5e
--- /dev/null
+++ b/chrome/browser/notifications/notification_ui_manager.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2009 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 "chrome/browser/notifications/notification_ui_manager.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/stl_util-inl.h"
+#include "chrome/browser/notifications/balloon_collection.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+
+// A class which represents a notification waiting to be shown.
+class QueuedNotification {
+ public:
+ QueuedNotification(const Notification& notification, Profile* profile)
+ : notification_(notification),
+ profile_(profile) {
+ }
+
+ const Notification& notification() const { return notification_; }
+ Profile* profile() const { return profile_; }
+
+ void Replace(const Notification& new_notification) {
+ notification_ = new_notification;
+ }
+
+ private:
+ // The notification to be shown.
+ Notification notification_;
+
+ // Non owned pointer to the user's profile.
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(QueuedNotification);
+};
+
+NotificationUIManager::NotificationUIManager()
+ : balloon_collection_(NULL) {
+}
+
+NotificationUIManager::~NotificationUIManager() {
+ STLDeleteElements(&show_queue_);
+}
+
+// static
+NotificationUIManager* NotificationUIManager::Create() {
+ BalloonCollection* balloons = BalloonCollection::Create();
+ NotificationUIManager* instance = new NotificationUIManager();
+ instance->Initialize(balloons);
+ balloons->set_space_change_listener(instance);
+ return instance;
+}
+
+void NotificationUIManager::Add(const Notification& notification,
+ Profile* profile) {
+ if (TryReplacement(notification)) {
+ return;
+ }
+
+ LOG(INFO) << "Added notification. URL: "
+ << notification.content_url().spec().c_str();
+ show_queue_.push_back(
+ new QueuedNotification(notification, profile));
+ CheckAndShowNotifications();
+}
+
+bool NotificationUIManager::Cancel(const Notification& notification) {
+ // First look through the notifications that haven't been shown. If not
+ // found there, call to the active balloon collection to tear it down.
+ NotificationDeque::iterator iter;
+ for (iter = show_queue_.begin(); iter != show_queue_.end(); ++iter) {
+ if (notification.IsSame((*iter)->notification())) {
+ show_queue_.erase(iter);
+ return true;
+ }
+ }
+ return balloon_collection_->Remove(notification);
+}
+
+void NotificationUIManager::CheckAndShowNotifications() {
+ // TODO(johnnyg): http://crbug.com/25061 - Check for user idle/presentation.
+ ShowNotifications();
+}
+
+void NotificationUIManager::ShowNotifications() {
+ while (!show_queue_.empty() && balloon_collection_->HasSpace()) {
+ scoped_ptr<QueuedNotification> queued_notification(show_queue_.front());
+ show_queue_.pop_front();
+ balloon_collection_->Add(queued_notification->notification(),
+ queued_notification->profile());
+ }
+}
+
+void NotificationUIManager::OnBalloonSpaceChanged() {
+ CheckAndShowNotifications();
+}
+
+bool NotificationUIManager::TryReplacement(const Notification& notification) {
+ const GURL& origin = notification.origin_url();
+ const string16& replace_id = notification.replace_id();
+
+ if (replace_id.empty())
+ return false;
+
+ // First check the queue of pending notifications for replacement.
+ // Then check the list of notifications already being shown.
+ NotificationDeque::iterator iter;
+ for (iter = show_queue_.begin(); iter != show_queue_.end(); ++iter) {
+ if (origin == (*iter)->notification().origin_url() &&
+ replace_id == (*iter)->notification().replace_id()) {
+ (*iter)->Replace(notification);
+ return true;
+ }
+ }
+
+ BalloonCollection::Balloons::iterator balloon_iter;
+ BalloonCollection::Balloons balloons =
+ balloon_collection_->GetActiveBalloons();
+ for (balloon_iter = balloons.begin();
+ balloon_iter != balloons.end();
+ ++balloon_iter) {
+ if (origin == (*balloon_iter)->notification().origin_url() &&
+ replace_id == (*balloon_iter)->notification().replace_id()) {
+ (*balloon_iter)->Update(notification);
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/chrome/browser/notifications/notification_ui_manager.h b/chrome/browser/notifications/notification_ui_manager.h
new file mode 100644
index 0000000..e510e7f
--- /dev/null
+++ b/chrome/browser/notifications/notification_ui_manager.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2009 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_UI_MANAGER_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_UI_MANAGER_H_
+
+#include <deque>
+
+#include "base/id_map.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/balloon_collection.h"
+
+class Notification;
+class Profile;
+class QueuedNotification;
+class SiteInstance;
+
+// The notification manager manages use of the desktop for notifications.
+// It maintains a queue of pending notifications when space becomes constrained.
+class NotificationUIManager
+ : public BalloonCollection::BalloonSpaceChangeListener {
+ public:
+ NotificationUIManager();
+ virtual ~NotificationUIManager();
+
+ // Creates an initialized UI manager with a new balloon collection
+ // and the listener relationship setup.
+ // Except for unit tests, this is the way to construct the object.
+ static NotificationUIManager* Create();
+
+ // Initializes the UI manager with a balloon collection; this object
+ // takes ownership of the balloon collection.
+ void Initialize(BalloonCollection* balloon_collection) {
+ DCHECK(!balloon_collection_.get());
+ DCHECK(balloon_collection);
+ balloon_collection_.reset(balloon_collection);
+ }
+
+ // Adds a notification to be displayed. Virtual for unit test override.
+ virtual void Add(const Notification& notification,
+ Profile* profile);
+
+ // Removes a notification.
+ virtual bool Cancel(const Notification& notification);
+
+ // Returns balloon collection.
+ BalloonCollection* balloon_collection() {
+ return balloon_collection_.get();
+ }
+
+ private:
+ // Attempts to display notifications from the show_queue if the user
+ // is active.
+ void CheckAndShowNotifications();
+
+ // Attempts to display notifications from the show_queue.
+ void ShowNotifications();
+
+ // BalloonCollectionObserver implementation.
+ virtual void OnBalloonSpaceChanged();
+
+ // Replace an existing notification with this one if applicable;
+ // returns true if the replacement happened.
+ bool TryReplacement(const Notification& notification);
+
+ // An owned pointer to the collection of active balloons.
+ scoped_ptr<BalloonCollection> balloon_collection_;
+
+ // A queue of notifications which are waiting to be shown.
+ typedef std::deque<QueuedNotification*> NotificationDeque;
+ NotificationDeque show_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationUIManager);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_UI_MANAGER_H_
diff --git a/chrome/browser/notifications/notifications_interactive_uitest.cc b/chrome/browser/notifications/notifications_interactive_uitest.cc
new file mode 100644
index 0000000..d8e4b27
--- /dev/null
+++ b/chrome/browser/notifications/notifications_interactive_uitest.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2006-2009 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 "base/file_path.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request_unittest.h"
+
+class NotificationsPermissionTest : public UITest {
+ public:
+ NotificationsPermissionTest() {
+ dom_automation_enabled_ = true;
+ show_window_ = true;
+ }
+};
+
+TEST_F(NotificationsPermissionTest, TestUserGestureInfobar) {
+ const wchar_t kDocRoot[] = L"chrome/test/data";
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(kDocRoot, NULL);
+ ASSERT_TRUE(server.get() != NULL);
+
+ scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+ scoped_refptr<TabProxy> tab(browser->GetActiveTab());
+ ASSERT_TRUE(tab.get());
+ ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS,
+ tab->NavigateToURL(server->TestServerPage(
+ "files/notifications/notifications_request_function.html")));
+ WaitUntilTabCount(1);
+
+ // Request permission by calling request() while eval'ing an inline script;
+ // That's considered a user gesture to webkit, and should produce an infobar.
+ bool result;
+ ASSERT_TRUE(tab->ExecuteAndExtractBool(
+ L"",
+ L"window.domAutomationController.send(request());",
+ &result));
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(tab->WaitForInfoBarCount(1, action_max_timeout_ms()));
+}
+
+TEST_F(NotificationsPermissionTest, TestNoUserGestureInfobar) {
+ const wchar_t kDocRoot[] = L"chrome/test/data";
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(kDocRoot, NULL);
+ ASSERT_TRUE(server.get() != NULL);
+
+ scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+ scoped_refptr<TabProxy> tab(browser->GetActiveTab());
+ ASSERT_TRUE(tab.get());
+
+ // Load a page which just does a request; no user gesture should result
+ // in no infobar.
+ ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS,
+ tab->NavigateToURL(server->TestServerPage(
+ "files/notifications/notifications_request_inline.html")));
+ WaitUntilTabCount(1);
+
+ int info_bar_count;
+ ASSERT_TRUE(tab->GetInfoBarCount(&info_bar_count));
+ EXPECT_EQ(0, info_bar_count);
+}
diff --git a/chrome/browser/notifications/notifications_prefs_cache.cc b/chrome/browser/notifications/notifications_prefs_cache.cc
new file mode 100644
index 0000000..2fec1e8
--- /dev/null
+++ b/chrome/browser/notifications/notifications_prefs_cache.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/notifications_prefs_cache.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/pref_service.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebNotificationPresenter.h"
+
+NotificationsPrefsCache::NotificationsPrefsCache()
+ : default_content_setting_(CONTENT_SETTING_DEFAULT),
+ is_initialized_(false) {
+}
+
+void NotificationsPrefsCache::CacheAllowedOrigin(
+ const GURL& origin) {
+ CheckThreadAccess();
+ std::set<GURL>::iterator iter;
+ allowed_origins_.insert(origin);
+ if ((iter = denied_origins_.find(origin)) != denied_origins_.end())
+ denied_origins_.erase(iter);
+}
+
+void NotificationsPrefsCache::CacheDeniedOrigin(
+ const GURL& origin) {
+ CheckThreadAccess();
+ std::set<GURL>::iterator iter;
+ denied_origins_.insert(origin);
+ if ((iter = allowed_origins_.find(origin)) != allowed_origins_.end())
+ allowed_origins_.erase(iter);
+}
+
+void NotificationsPrefsCache::SetCacheAllowedOrigins(
+ const std::vector<GURL>& allowed) {
+ allowed_origins_.clear();
+ allowed_origins_.insert(allowed.begin(), allowed.end());
+}
+
+void NotificationsPrefsCache::SetCacheDeniedOrigins(
+ const std::vector<GURL>& denied) {
+ denied_origins_.clear();
+ denied_origins_.insert(denied.begin(), denied.end());
+}
+
+void NotificationsPrefsCache::SetCacheDefaultContentSetting(
+ ContentSetting setting) {
+ default_content_setting_ = setting;
+}
+
+// static
+void NotificationsPrefsCache::ListValueToGurlVector(
+ const ListValue& origin_list,
+ std::vector<GURL>* origin_vector) {
+ ListValue::const_iterator i;
+ std::wstring origin;
+ for (i = origin_list.begin(); i != origin_list.end(); ++i) {
+ (*i)->GetAsString(&origin);
+ origin_vector->push_back(GURL(WideToUTF8(origin)));
+ }
+}
+
+int NotificationsPrefsCache::HasPermission(const GURL& origin) {
+ if (IsOriginAllowed(origin))
+ return WebKit::WebNotificationPresenter::PermissionAllowed;
+ if (IsOriginDenied(origin))
+ return WebKit::WebNotificationPresenter::PermissionDenied;
+ switch (default_content_setting_) {
+ case CONTENT_SETTING_ALLOW:
+ return WebKit::WebNotificationPresenter::PermissionAllowed;
+ case CONTENT_SETTING_BLOCK:
+ return WebKit::WebNotificationPresenter::PermissionDenied;
+ case CONTENT_SETTING_ASK:
+ case CONTENT_SETTING_DEFAULT:
+ default: // Make gcc happy.
+ return WebKit::WebNotificationPresenter::PermissionNotAllowed;
+ }
+}
+
+bool NotificationsPrefsCache::IsOriginAllowed(
+ const GURL& origin) {
+ CheckThreadAccess();
+ return allowed_origins_.find(origin) != allowed_origins_.end();
+}
+
+bool NotificationsPrefsCache::IsOriginDenied(
+ const GURL& origin) {
+ CheckThreadAccess();
+ return denied_origins_.find(origin) != denied_origins_.end();
+}
+
+void NotificationsPrefsCache::CheckThreadAccess() {
+ if (is_initialized_) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+ } else {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ }
+}
diff --git a/chrome/browser/notifications/notifications_prefs_cache.h b/chrome/browser/notifications/notifications_prefs_cache.h
new file mode 100644
index 0000000..61bb983
--- /dev/null
+++ b/chrome/browser/notifications/notifications_prefs_cache.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NOTIFICATIONS_NOTIFICATIONS_PREFS_CACHE_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATIONS_PREFS_CACHE_H_
+
+#include <set>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "chrome/common/content_settings.h"
+#include "googleurl/src/gurl.h"
+
+class ListValue;
+
+// Class which caches notification preferences.
+// Construction occurs on the UI thread when the contents
+// of the profile preferences are initialized. Once is_initialized() is set,
+// access can only be done from the IO thread.
+class NotificationsPrefsCache
+ : public base::RefCountedThreadSafe<NotificationsPrefsCache> {
+ public:
+ NotificationsPrefsCache();
+
+ // Once is_initialized() is set, all accesses must happen on the IO thread.
+ // Before that, all accesses need to happen on the UI thread.
+ void set_is_initialized(bool val) { is_initialized_ = val; }
+ bool is_initialized() { return is_initialized_; }
+
+ // Checks to see if a given origin has permission to create desktop
+ // notifications. Returns a constant from WebNotificationPresenter
+ // class.
+ int HasPermission(const GURL& origin);
+
+ // Updates the cache with a new origin allowed or denied.
+ void CacheAllowedOrigin(const GURL& origin);
+ void CacheDeniedOrigin(const GURL& origin);
+
+ // Set the cache to the supplied values. This clears the current
+ // contents of the cache.
+ void SetCacheAllowedOrigins(const std::vector<GURL>& allowed);
+ void SetCacheDeniedOrigins(const std::vector<GURL>& denied);
+ void SetCacheDefaultContentSetting(ContentSetting setting);
+
+ static void ListValueToGurlVector(const ListValue& origin_list,
+ std::vector<GURL>* origin_vector);
+
+ // Exposed for testing.
+ ContentSetting CachedDefaultContentSetting() {
+ return default_content_setting_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<NotificationsPrefsCache>;
+
+ ~NotificationsPrefsCache() {}
+
+ // Helper functions which read preferences.
+ bool IsOriginAllowed(const GURL& origin);
+ bool IsOriginDenied(const GURL& origin);
+
+ // Helper that ensures we are running on the expected thread.
+ void CheckThreadAccess();
+
+ // Storage of the actual preferences.
+ std::set<GURL> allowed_origins_;
+ std::set<GURL> denied_origins_;
+
+ // The default setting, used for origins that are neither in
+ // |allowed_origins_| nor |denied_origins_|.
+ ContentSetting default_content_setting_;
+
+ // Set to true once the initial cached settings have been completely read.
+ // Once this is done, the class can no longer be accessed on the UI thread.
+ bool is_initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationsPrefsCache);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATIONS_PREFS_CACHE_H_
diff --git a/chrome/browser/notifications/notifications_prefs_cache_unittest.cc b/chrome/browser/notifications/notifications_prefs_cache_unittest.cc
new file mode 100644
index 0000000..0cbea9e
--- /dev/null
+++ b/chrome/browser/notifications/notifications_prefs_cache_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2010 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 "chrome/browser/notifications/notifications_prefs_cache.h"
+
+#include "base/message_loop.h"
+#include "chrome/browser/chrome_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebNotificationPresenter.h"
+
+TEST(NotificationsPrefsCacheTest, CanCreate) {
+ scoped_refptr<NotificationsPrefsCache> cache(new NotificationsPrefsCache());
+ std::vector<GURL> allowed_origins;
+ allowed_origins.push_back(GURL("http://allowed.com"));
+ std::vector<GURL> denied_origins;
+ denied_origins.push_back(GURL("http://denied.com"));
+
+ {
+ MessageLoop loop;
+ ChromeThread ui_thread(ChromeThread::UI, &loop);
+
+ cache->SetCacheAllowedOrigins(allowed_origins);
+ cache->SetCacheDeniedOrigins(denied_origins);
+ cache->SetCacheDefaultContentSetting(CONTENT_SETTING_DEFAULT);
+ }
+
+ cache->set_is_initialized(true);
+
+ {
+ MessageLoop loop;
+ ChromeThread io_thread(ChromeThread::IO, &loop);
+
+ cache->CacheAllowedOrigin(GURL("http://allowed2.com"));
+ cache->CacheDeniedOrigin(GURL("http://denied2.com"));
+
+ EXPECT_EQ(cache->HasPermission(GURL("http://allowed.com")),
+ WebKit::WebNotificationPresenter::PermissionAllowed);
+ EXPECT_EQ(cache->HasPermission(GURL("http://allowed2.com")),
+ WebKit::WebNotificationPresenter::PermissionAllowed);
+
+ EXPECT_EQ(cache->HasPermission(GURL("http://denied.com")),
+ WebKit::WebNotificationPresenter::PermissionDenied);
+ EXPECT_EQ(cache->HasPermission(GURL("http://denied2.com")),
+ WebKit::WebNotificationPresenter::PermissionDenied);
+
+ EXPECT_EQ(cache->HasPermission(GURL("http://unkown.com")),
+ WebKit::WebNotificationPresenter::PermissionNotAllowed);
+
+ cache->SetCacheDefaultContentSetting(CONTENT_SETTING_ASK);
+ EXPECT_EQ(cache->HasPermission(GURL("http://unkown.com")),
+ WebKit::WebNotificationPresenter::PermissionNotAllowed);
+
+ cache->SetCacheDefaultContentSetting(CONTENT_SETTING_ALLOW);
+ EXPECT_EQ(cache->HasPermission(GURL("http://unkown.com")),
+ WebKit::WebNotificationPresenter::PermissionAllowed);
+
+ cache->SetCacheDefaultContentSetting(CONTENT_SETTING_BLOCK);
+ EXPECT_EQ(cache->HasPermission(GURL("http://unkown.com")),
+ WebKit::WebNotificationPresenter::PermissionDenied);
+ }
+}
+