summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-11 04:49:23 +0000
committerjianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-11 04:49:23 +0000
commit1311a0786ef2de3cb44ca9684b8d66ed1013e01b (patch)
tree95b4ad8e1d25da74c5db3677d3480dda0dbb14b3
parent61df7d1f919ab65a45ffca7b031b962a95474537 (diff)
downloadchromium_src-1311a0786ef2de3cb44ca9684b8d66ed1013e01b.zip
chromium_src-1311a0786ef2de3cb44ca9684b8d66ed1013e01b.tar.gz
chromium_src-1311a0786ef2de3cb44ca9684b8d66ed1013e01b.tar.bz2
Fix the problem that notifications and panels overlap.
The workaround is to move all notifications on top of the panels that could overlap with. BUG=103052 TEST=new test Review URL: http://codereview.chromium.org/8503037 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@109588 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/notifications/balloon_collection_gtk.cc9
-rw-r--r--chrome/browser/notifications/balloon_collection_impl.cc138
-rw-r--r--chrome/browser/notifications/balloon_collection_impl.h25
-rw-r--r--chrome/browser/notifications/balloon_collection_mac.mm11
-rw-r--r--chrome/browser/notifications/balloon_collection_views.cc9
-rw-r--r--chrome/browser/ui/panels/base_panel_browser_test.cc30
-rw-r--r--chrome/browser/ui/panels/base_panel_browser_test.h5
-rw-r--r--chrome/browser/ui/panels/panel.cc5
-rw-r--r--chrome/browser/ui/panels/panel_browsertest.cc170
-rw-r--r--chrome/browser/ui/panels/panel_manager.cc14
-rw-r--r--chrome/browser/ui/panels/panel_manager.h2
-rw-r--r--chrome/common/chrome_notification_types.h15
12 files changed, 408 insertions, 25 deletions
diff --git a/chrome/browser/notifications/balloon_collection_gtk.cc b/chrome/browser/notifications/balloon_collection_gtk.cc
index 2420735..5b8a784 100644
--- a/chrome/browser/notifications/balloon_collection_gtk.cc
+++ b/chrome/browser/notifications/balloon_collection_gtk.cc
@@ -35,6 +35,14 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const {
return 5;
}
+bool BalloonCollectionImpl::Layout::NeedToMoveAboveLeftSidePanels() const {
+ return placement_ == VERTICALLY_FROM_BOTTOM_LEFT;
+}
+
+bool BalloonCollectionImpl::Layout::NeedToMoveAboveRightSidePanels() const {
+ return placement_ == VERTICALLY_FROM_BOTTOM_RIGHT;
+}
+
void BalloonCollectionImpl::PositionBalloons(bool reposition) {
PositionBalloonsInternal(reposition);
}
@@ -92,6 +100,7 @@ void BalloonCollectionImpl::SetPositionPreference(
else
NOTREACHED();
+ layout_.ComputeOffsetToMoveAbovePanels(gfx::Rect());
PositionBalloons(true);
}
diff --git a/chrome/browser/notifications/balloon_collection_impl.cc b/chrome/browser/notifications/balloon_collection_impl.cc
index 9d234c2..173c78c 100644
--- a/chrome/browser/notifications/balloon_collection_impl.cc
+++ b/chrome/browser/notifications/balloon_collection_impl.cc
@@ -10,7 +10,13 @@
#include "chrome/browser/notifications/balloon.h"
#include "chrome/browser/notifications/balloon_host.h"
#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/panels/panel_manager.h"
+#include "chrome/browser/ui/panels/panel.h"
#include "chrome/browser/ui/window_sizer.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_service.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
@@ -27,6 +33,9 @@ const int kMinAllowedBalloonCount = 2;
// there is a relayout, in milliseconds.
const int kRepositionDelay = 300;
+// The spacing between the balloon and the panel.
+const int kVerticalSpacingBetweenBalloonAndPanel = 5;
+
} // namespace
BalloonCollectionImpl::BalloonCollectionImpl()
@@ -35,6 +44,12 @@ BalloonCollectionImpl::BalloonCollectionImpl()
added_as_message_loop_observer_(false)
#endif
{
+ registrar_.Add(this, chrome::NOTIFICATION_PANEL_ADDED,
+ content::NotificationService::AllSources());
+ registrar_.Add(this, chrome::NOTIFICATION_PANEL_REMOVED,
+ content::NotificationService::AllSources());
+ registrar_.Add(this, chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS,
+ content::NotificationService::AllSources());
SetPositionPreference(BalloonCollection::DEFAULT_POSITION);
}
@@ -153,6 +168,26 @@ const BalloonCollection::Balloons& BalloonCollectionImpl::GetActiveBalloons() {
return base_.balloons();
}
+void BalloonCollectionImpl::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ gfx::Rect bounds;
+ switch (type) {
+ case chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS:
+ bounds = content::Source<Panel>(source).ptr()->GetBounds();
+ // Fall through.
+ case chrome::NOTIFICATION_PANEL_ADDED:
+ case chrome::NOTIFICATION_PANEL_REMOVED:
+ if (layout_.ComputeOffsetToMoveAbovePanels(bounds))
+ PositionBalloons(true);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
void BalloonCollectionImpl::PositionBalloonsInternal(bool reposition) {
const Balloons& balloons = base_.balloons();
@@ -231,7 +266,9 @@ void BalloonCollectionImpl::HandleMouseMoveEvent() {
}
#endif
-BalloonCollectionImpl::Layout::Layout() : placement_(INVALID) {
+BalloonCollectionImpl::Layout::Layout()
+ : placement_(INVALID),
+ offset_to_move_above_panels_(0) {
RefreshSystemMetrics();
}
@@ -245,24 +282,30 @@ void BalloonCollectionImpl::Layout::GetMaxLinearSize(int* max_balloon_size,
}
gfx::Point BalloonCollectionImpl::Layout::GetLayoutOrigin() const {
+ // For lower-left and lower-right positioning, we need to add an offset
+ // to ensure balloons to stay on top of panels to avoid overlapping.
int x = 0;
int y = 0;
switch (placement_) {
- case VERTICALLY_FROM_TOP_LEFT:
+ case VERTICALLY_FROM_TOP_LEFT: {
x = work_area_.x() + HorizontalEdgeMargin();
- y = work_area_.y() + VerticalEdgeMargin();
+ y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_;
break;
- case VERTICALLY_FROM_TOP_RIGHT:
+ }
+ case VERTICALLY_FROM_TOP_RIGHT: {
x = work_area_.right() - HorizontalEdgeMargin();
- y = work_area_.y() + VerticalEdgeMargin();
+ y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_;
break;
+ }
case VERTICALLY_FROM_BOTTOM_LEFT:
x = work_area_.x() + HorizontalEdgeMargin();
- y = work_area_.bottom() - VerticalEdgeMargin();
+ y = work_area_.bottom() - VerticalEdgeMargin() -
+ offset_to_move_above_panels_;
break;
case VERTICALLY_FROM_BOTTOM_RIGHT:
x = work_area_.right() - HorizontalEdgeMargin();
- y = work_area_.bottom() - VerticalEdgeMargin();
+ y = work_area_.bottom() - VerticalEdgeMargin() -
+ offset_to_move_above_panels_;
break;
default:
NOTREACHED();
@@ -311,30 +354,21 @@ gfx::Point BalloonCollectionImpl::Layout::NextPosition(
}
gfx::Point BalloonCollectionImpl::Layout::OffScreenLocation() const {
- int x = 0;
- int y = 0;
+ gfx::Point location = GetLayoutOrigin();
switch (placement_) {
case VERTICALLY_FROM_TOP_LEFT:
- x = work_area_.x() + HorizontalEdgeMargin();
- y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin();
- break;
- case VERTICALLY_FROM_TOP_RIGHT:
- x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin();
- y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin();
- break;
case VERTICALLY_FROM_BOTTOM_LEFT:
- x = work_area_.x() + HorizontalEdgeMargin();
- y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin();
+ location.Offset(0, kBalloonMaxHeight);
break;
+ case VERTICALLY_FROM_TOP_RIGHT:
case VERTICALLY_FROM_BOTTOM_RIGHT:
- x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin();
- y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin();
+ location.Offset(-kBalloonMaxWidth, kBalloonMaxHeight);
break;
default:
NOTREACHED();
break;
}
- return gfx::Point(x, y);
+ return location;
}
bool BalloonCollectionImpl::Layout::RequiresOffsets() const {
@@ -363,6 +397,68 @@ gfx::Size BalloonCollectionImpl::Layout::ConstrainToSizeLimits(
std::min(max_balloon_height(), size.height())));
}
+bool BalloonCollectionImpl::Layout::ComputeOffsetToMoveAbovePanels(
+ const gfx::Rect& panel_bounds) {
+ const PanelManager::Panels& panels = PanelManager::GetInstance()->panels();
+ int offset_to_move_above_panels = 0;
+
+ // The offset is the maximum height of panels that could overlap with the
+ // balloons.
+ if (NeedToMoveAboveLeftSidePanels()) {
+ // If the affecting panel does not lie in the balloon area, no need to
+ // update the offset.
+ if (!panel_bounds.IsEmpty() &&
+ (panel_bounds.right() <= work_area_.x() ||
+ panel_bounds.x() >= work_area_.x() + max_balloon_width())) {
+ return false;
+ }
+
+ for (PanelManager::Panels::const_reverse_iterator iter = panels.rbegin();
+ iter != panels.rend(); ++iter) {
+ // No need to check panels beyond the area occupied by the balloons.
+ if ((*iter)->GetBounds().x() >= work_area_.x() + max_balloon_width())
+ break;
+
+ int current_height = (*iter)->GetBounds().height();
+ if (current_height > offset_to_move_above_panels)
+ offset_to_move_above_panels = current_height;
+ }
+ } else if (NeedToMoveAboveRightSidePanels()) {
+ // If the affecting panel does not lie in the balloon area, no need to
+ // update the offset.
+ if (!panel_bounds.IsEmpty() &&
+ (panel_bounds.right() <= work_area_.right() - max_balloon_width() ||
+ panel_bounds.x() >= work_area_.right())) {
+ return false;
+ }
+
+ for (PanelManager::Panels::const_iterator iter = panels.begin();
+ iter != panels.end(); ++iter) {
+ // No need to check panels beyond the area occupied by the balloons.
+ if ((*iter)->GetBounds().right() <=
+ work_area_.right() - max_balloon_width())
+ break;
+
+ int current_height = (*iter)->GetBounds().height();
+ if (current_height > offset_to_move_above_panels)
+ offset_to_move_above_panels = current_height;
+ }
+ }
+
+ // Ensure that we have some sort of margin between the 1st balloon and the
+ // panel beneath it even the vertical edge margin is 0 as on Mac.
+ if (offset_to_move_above_panels && !VerticalEdgeMargin())
+ offset_to_move_above_panels += kVerticalSpacingBetweenBalloonAndPanel;
+
+ // If no change is detected, return false to indicate that we do not need to
+ // reposition balloons.
+ if (offset_to_move_above_panels_ == offset_to_move_above_panels)
+ return false;
+
+ offset_to_move_above_panels_ = offset_to_move_above_panels;
+ return true;
+}
+
bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() {
bool changed = false;
diff --git a/chrome/browser/notifications/balloon_collection_impl.h b/chrome/browser/notifications/balloon_collection_impl.h
index 887d1f2..424351c 100644
--- a/chrome/browser/notifications/balloon_collection_impl.h
+++ b/chrome/browser/notifications/balloon_collection_impl.h
@@ -16,6 +16,8 @@
#include "base/message_loop.h"
#include "chrome/browser/notifications/balloon_collection.h"
#include "chrome/browser/notifications/balloon_collection_base.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
@@ -32,7 +34,8 @@
// 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
+class BalloonCollectionImpl : public BalloonCollection,
+ public content::NotificationObserver
#if USE_OFFSETS
, public MessageLoopForUI::Observer
#endif
@@ -54,6 +57,11 @@ class BalloonCollectionImpl : public BalloonCollection
virtual void OnBalloonClosed(Balloon* source);
virtual const Balloons& GetActiveBalloons();
+ // content::NotificationObserver interface.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
// MessageLoopForUI::Observer interface.
#if defined(OS_WIN) || defined(TOUCH_UI) || defined(USE_AURA)
virtual base::EventStatus WillProcessEvent(
@@ -127,12 +135,19 @@ class BalloonCollectionImpl : public BalloonCollection
// buttons under the cursor during rapid-close interaction.
bool RequiresOffsets() const;
+ // Returns true if there is change to the offset that requires the balloons
+ // to be repositioned.
+ bool ComputeOffsetToMoveAbovePanels(const gfx::Rect& panel_bounds);
+
private:
// Layout parameters
int VerticalEdgeMargin() const;
int HorizontalEdgeMargin() const;
int InterBalloonMargin() const;
+ bool NeedToMoveAboveLeftSidePanels() const;
+ bool NeedToMoveAboveRightSidePanels() const;
+
// Minimum and maximum size of balloon content.
static const int kBalloonMinWidth = 300;
static const int kBalloonMaxWidth = 300;
@@ -141,6 +156,12 @@ class BalloonCollectionImpl : public BalloonCollection
Placement placement_;
gfx::Rect work_area_;
+
+ // The offset that guarantees that the notificaitions shown in the
+ // lower-right or lower-left corner of the screen will go above currently
+ // shown panels and will not be obscured by them.
+ int offset_to_move_above_panels_;
+
DISALLOW_COPY_AND_ASSIGN(Layout);
};
@@ -171,6 +192,8 @@ class BalloonCollectionImpl : public BalloonCollection
// The layout parameters for balloons in this collection.
Layout layout_;
+ content::NotificationRegistrar registrar_;
+
#if USE_OFFSETS
// Start and stop observing all UI events.
void AddMessageLoopObserver();
diff --git a/chrome/browser/notifications/balloon_collection_mac.mm b/chrome/browser/notifications/balloon_collection_mac.mm
index d31bc39..a30b689 100644
--- a/chrome/browser/notifications/balloon_collection_mac.mm
+++ b/chrome/browser/notifications/balloon_collection_mac.mm
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
@@ -35,6 +35,14 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const {
return 0;
}
+bool BalloonCollectionImpl::Layout::NeedToMoveAboveLeftSidePanels() const {
+ return placement_ == VERTICALLY_FROM_TOP_LEFT;
+}
+
+bool BalloonCollectionImpl::Layout::NeedToMoveAboveRightSidePanels() const {
+ return placement_ == VERTICALLY_FROM_TOP_RIGHT;
+}
+
void BalloonCollectionImpl::PositionBalloons(bool reposition) {
// Use an animation context so that all the balloons animate together.
[NSAnimationContext beginGrouping];
@@ -61,6 +69,7 @@ void BalloonCollectionImpl::SetPositionPreference(
else
NOTREACHED();
+ layout_.ComputeOffsetToMoveAbovePanels(gfx::Rect());
PositionBalloons(true);
}
diff --git a/chrome/browser/notifications/balloon_collection_views.cc b/chrome/browser/notifications/balloon_collection_views.cc
index c436df88..f5ac0e4 100644
--- a/chrome/browser/notifications/balloon_collection_views.cc
+++ b/chrome/browser/notifications/balloon_collection_views.cc
@@ -31,6 +31,14 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const {
return 0;
}
+bool BalloonCollectionImpl::Layout::NeedToMoveAboveLeftSidePanels() const {
+ return placement_ == VERTICALLY_FROM_BOTTOM_LEFT;
+}
+
+bool BalloonCollectionImpl::Layout::NeedToMoveAboveRightSidePanels() const {
+ return placement_ == VERTICALLY_FROM_BOTTOM_RIGHT;
+}
+
void BalloonCollectionImpl::PositionBalloons(bool reposition) {
PositionBalloonsInternal(reposition);
}
@@ -96,6 +104,7 @@ void BalloonCollectionImpl::SetPositionPreference(
else
NOTREACHED();
+ layout_.ComputeOffsetToMoveAbovePanels(gfx::Rect());
PositionBalloons(true);
}
diff --git a/chrome/browser/ui/panels/base_panel_browser_test.cc b/chrome/browser/ui/panels/base_panel_browser_test.cc
index bcfbee0..992557b 100644
--- a/chrome/browser/ui/panels/base_panel_browser_test.cc
+++ b/chrome/browser/ui/panels/base_panel_browser_test.cc
@@ -148,6 +148,11 @@ void MockAutoHidingDesktopBarImpl::NotifyThicknessChange() {
observer_->OnAutoHidingDesktopBarThicknessChanged();
}
+bool ExistsPanel(Panel* panel) {
+ const PanelManager::Panels& panels = PanelManager::GetInstance()->panels();
+ return find(panels.begin(), panels.end(), panel) != panels.end();
+}
+
} // namespace
BasePanelBrowserTest::BasePanelBrowserTest()
@@ -175,7 +180,10 @@ void BasePanelBrowserTest::SetUpOnMainThread() {
mock_auto_hiding_desktop_bar_ = new MockAutoHidingDesktopBarImpl(
panel_manager);
panel_manager->set_auto_hiding_desktop_bar(mock_auto_hiding_desktop_bar_);
- panel_manager->SetWorkAreaForTesting(testing_work_area_);
+ // Do not use the testing work area if it is empty since we're going to
+ // use the actual work area in some testing scenarios.
+ if (!testing_work_area_.IsEmpty())
+ panel_manager->SetWorkAreaForTesting(testing_work_area_);
panel_manager->enable_auto_sizing(false);
panel_manager->remove_delays_for_testing();
// This is needed so the subsequently created panels can be activated.
@@ -183,6 +191,26 @@ void BasePanelBrowserTest::SetUpOnMainThread() {
EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
}
+void BasePanelBrowserTest::WaitForPanelAdded(Panel* panel) {
+ if (ExistsPanel(panel))
+ return;
+ ui_test_utils::WindowedNotificationObserver signal(
+ chrome::NOTIFICATION_PANEL_ADDED,
+ content::Source<Panel>(panel));
+ signal.Wait();
+ EXPECT_TRUE(ExistsPanel(panel));
+}
+
+void BasePanelBrowserTest::WaitForPanelRemoved(Panel* panel) {
+ if (!ExistsPanel(panel))
+ return;
+ ui_test_utils::WindowedNotificationObserver signal(
+ chrome::NOTIFICATION_PANEL_REMOVED,
+ content::Source<Panel>(panel));
+ signal.Wait();
+ EXPECT_FALSE(ExistsPanel(panel));
+}
+
void BasePanelBrowserTest::WaitForPanelActiveState(
Panel* panel, ActiveState expected_state) {
DCHECK(expected_state == SHOW_AS_ACTIVE ||
diff --git a/chrome/browser/ui/panels/base_panel_browser_test.h b/chrome/browser/ui/panels/base_panel_browser_test.h
index 42cf7e0..a833706 100644
--- a/chrome/browser/ui/panels/base_panel_browser_test.h
+++ b/chrome/browser/ui/panels/base_panel_browser_test.h
@@ -58,6 +58,8 @@ class BasePanelBrowserTest : public InProcessBrowserTest {
const gfx::Rect& bounds);
Panel* CreatePanel(const std::string& panel_name);
+ void WaitForPanelAdded(Panel* panel);
+ void WaitForPanelRemoved(Panel* panel);
void WaitForPanelActiveState(Panel* panel, ActiveState state);
void WaitForWindowSizeAvailable(Panel* panel);
void WaitForBoundsAnimationFinished(Panel* panel);
@@ -69,6 +71,9 @@ class BasePanelBrowserTest : public InProcessBrowserTest {
const DictionaryValue& extra_value);
gfx::Rect testing_work_area() const { return testing_work_area_; }
+ void set_testing_work_area(const gfx::Rect& work_area) {
+ testing_work_area_ = work_area;
+ }
MockAutoHidingDesktopBar* mock_auto_hiding_desktop_bar() const {
return mock_auto_hiding_desktop_bar_.get();
diff --git a/chrome/browser/ui/panels/panel.cc b/chrome/browser/ui/panels/panel.cc
index b231136..976ede4 100644
--- a/chrome/browser/ui/panels/panel.cc
+++ b/chrome/browser/ui/panels/panel.cc
@@ -63,6 +63,11 @@ void Panel::SetPanelBounds(const gfx::Rect& bounds) {
restored_height_ = bounds.height();
native_panel_->SetPanelBounds(bounds);
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS,
+ content::Source<Panel>(this),
+ content::NotificationService::NoDetails());
}
void Panel::SetMaxSize(const gfx::Size& max_size) {
diff --git a/chrome/browser/ui/panels/panel_browsertest.cc b/chrome/browser/ui/panels/panel_browsertest.cc
index 382642c..8b146be 100644
--- a/chrome/browser/ui/panels/panel_browsertest.cc
+++ b/chrome/browser/ui/panels/panel_browsertest.cc
@@ -4,9 +4,16 @@
#include "base/bind.h"
#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_service.h"
#include "chrome/browser/download/download_service_factory.h"
#include "chrome/browser/net/url_request_mock_util.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_ui_manager.h"
+#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_list.h"
@@ -26,9 +33,11 @@
#include "content/browser/download/download_manager.h"
#include "content/browser/net/url_request_mock_http_job.h"
#include "content/browser/tab_contents/tab_contents.h"
+#include "content/common/desktop_notification_messages.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/url_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/screen.h"
using content::BrowserThread;
@@ -1565,3 +1574,164 @@ IN_PROC_BROWSER_TEST_F(PanelDownloadTest, DownloadNoTabbedBrowser) {
panel_browser->CloseWindow();
}
+
+class PanelAndNotificationTest : public PanelBrowserTest {
+ public:
+ PanelAndNotificationTest() : PanelBrowserTest() {
+ // Do not use our own testing work area since desktop notification code
+ // does not have the hook up for testing work area.
+ set_testing_work_area(gfx::Rect());
+ }
+
+ virtual ~PanelAndNotificationTest() {
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ g_browser_process->local_state()->SetInteger(
+ prefs::kDesktopNotificationPosition, BalloonCollection::LOWER_RIGHT);
+ balloons_ = new BalloonCollectionImpl();
+ ui_manager_.reset(NotificationUIManager::Create(
+ g_browser_process->local_state(), balloons_));
+ service_.reset(new DesktopNotificationService(browser()->profile(),
+ ui_manager_.get()));
+
+ PanelBrowserTest::SetUpOnMainThread();
+ }
+
+ virtual void CleanUpOnMainThread() OVERRIDE {
+ balloons_->RemoveAll();
+ MessageLoopForUI::current()->RunAllPending();
+
+ service_.reset();
+ ui_manager_.reset();
+
+ PanelBrowserTest::CleanUpOnMainThread();
+ }
+
+ DesktopNotificationHostMsg_Show_Params StandardTestNotification() {
+ DesktopNotificationHostMsg_Show_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;
+ }
+
+ int GetBalloonBottomPosition(Balloon* balloon) const {
+#if defined(OS_MACOSX)
+ // The position returned by the notification balloon is based on Mac's
+ // vertically inverted orientation. We need to flip it so that it can
+ // be compared against the position returned by the panel.
+ gfx::Size screen_size = gfx::Screen::GetPrimaryMonitorSize();
+ return screen_size.height() - balloon->GetPosition().y();
+#else
+ return balloon->GetPosition().y() + balloon->GetViewSize().height();
+#endif
+ }
+
+ DesktopNotificationService* service() const { return service_.get(); }
+ const BalloonCollection::Balloons& balloons() const {
+ return balloons_->GetActiveBalloons();
+ }
+
+ private:
+ BalloonCollectionImpl* balloons_; // Owned by NotificationUIManager.
+ scoped_ptr<NotificationUIManager> ui_manager_;
+ scoped_ptr<DesktopNotificationService> service_;
+};
+
+IN_PROC_BROWSER_TEST_F(PanelAndNotificationTest, NoOverlapping) {
+ const int kPanelWidth = 200;
+ const int kShortPanelHeight = 150;
+ const int kTallPanelHeight = 200;
+
+ DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
+ EXPECT_TRUE(service()->ShowDesktopNotification(
+ params, 0, 0, DesktopNotificationService::PageNotification));
+ MessageLoopForUI::current()->RunAllPending();
+ Balloon* balloon = balloons().front();
+ int original_balloon_bottom = GetBalloonBottomPosition(balloon);
+ // Ensure that balloon width is greater than the panel width.
+ EXPECT_GT(balloon->GetViewSize().width(), kPanelWidth);
+
+ // Creating a short panel should move the notification balloon up.
+ Panel* panel1 = CreatePanelWithBounds(
+ "Panel1", gfx::Rect(0, 0, kPanelWidth, kShortPanelHeight));
+ WaitForPanelAdded(panel1);
+ int balloon_bottom_after_short_panel_created =
+ GetBalloonBottomPosition(balloon);
+ EXPECT_LT(balloon_bottom_after_short_panel_created, panel1->GetBounds().y());
+ EXPECT_LT(balloon_bottom_after_short_panel_created, original_balloon_bottom);
+
+ // Creating another tall panel should move the notification balloon further
+ // up.
+ Panel* panel2 = CreatePanelWithBounds(
+ "Panel2", gfx::Rect(0, 0, kPanelWidth, kTallPanelHeight));
+ WaitForPanelAdded(panel2);
+ int balloon_bottom_after_tall_panel_created =
+ GetBalloonBottomPosition(balloon);
+ EXPECT_LT(balloon_bottom_after_tall_panel_created, panel2->GetBounds().y());
+ EXPECT_LT(balloon_bottom_after_tall_panel_created,
+ balloon_bottom_after_short_panel_created);
+
+ // Minimizing tall panel should move the notification balloon down to the same
+ // position when short panel is first created.
+ panel2->SetExpansionState(Panel::MINIMIZED);
+ WaitForBoundsAnimationFinished(panel2);
+ int balloon_bottom_after_tall_panel_minimized =
+ GetBalloonBottomPosition(balloon);
+ EXPECT_EQ(balloon_bottom_after_short_panel_created,
+ balloon_bottom_after_tall_panel_minimized);
+
+ // Minimizing short panel should move the notification balloon further down.
+ panel1->SetExpansionState(Panel::MINIMIZED);
+ WaitForBoundsAnimationFinished(panel1);
+ int balloon_bottom_after_both_panels_minimized =
+ GetBalloonBottomPosition(balloon);
+ EXPECT_LT(balloon_bottom_after_both_panels_minimized,
+ panel1->GetBounds().y());
+ EXPECT_LT(balloon_bottom_after_both_panels_minimized,
+ panel2->GetBounds().y());
+ EXPECT_LT(balloon_bottom_after_short_panel_created,
+ balloon_bottom_after_both_panels_minimized);
+ EXPECT_LT(balloon_bottom_after_both_panels_minimized,
+ original_balloon_bottom);
+
+ // Bringing up the titlebar for tall panel should move the notification
+ // balloon up a little bit.
+ panel2->SetExpansionState(Panel::TITLE_ONLY);
+ WaitForBoundsAnimationFinished(panel2);
+ int balloon_bottom_after_tall_panel_titlebar_up =
+ GetBalloonBottomPosition(balloon);
+ EXPECT_LT(balloon_bottom_after_tall_panel_titlebar_up,
+ panel2->GetBounds().y());
+ EXPECT_LT(balloon_bottom_after_tall_panel_titlebar_up,
+ balloon_bottom_after_both_panels_minimized);
+ EXPECT_LT(balloon_bottom_after_short_panel_created,
+ balloon_bottom_after_tall_panel_titlebar_up);
+
+ // Expanding short panel should move the notification balloon further up to
+ // the same position when short panel is first created.
+ panel1->SetExpansionState(Panel::EXPANDED);
+ WaitForBoundsAnimationFinished(panel1);
+ int balloon_bottom_after_short_panel_expanded =
+ GetBalloonBottomPosition(balloon);
+ EXPECT_EQ(balloon_bottom_after_short_panel_created,
+ balloon_bottom_after_short_panel_expanded);
+
+ // Closing short panel should move the notification balloon down to the same
+ // position when tall panel brings up its titlebar.
+ panel1->Close();
+ WaitForBoundsAnimationFinished(panel1);
+ EXPECT_EQ(balloon_bottom_after_tall_panel_titlebar_up,
+ GetBalloonBottomPosition(balloon));
+
+ // Closing the remaining tall panel should move the notification balloon back
+ // to its original position.
+ panel2->Close();
+ WaitForBoundsAnimationFinished(panel2);
+ EXPECT_EQ(original_balloon_bottom, GetBalloonBottomPosition(balloon));
+}
diff --git a/chrome/browser/ui/panels/panel_manager.cc b/chrome/browser/ui/panels/panel_manager.cc
index 0f4c0d2..e37eb43 100644
--- a/chrome/browser/ui/panels/panel_manager.cc
+++ b/chrome/browser/ui/panels/panel_manager.cc
@@ -13,6 +13,9 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/window_sizer.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
namespace {
// Invalid panel index.
@@ -120,6 +123,12 @@ Panel* PanelManager::CreatePanel(Browser* browser) {
Panel* panel = new Panel(browser, gfx::Rect(x, y, width, height));
panel->SetMaxSize(gfx::Size(max_panel_width, max_panel_height));
panels_.push_back(panel);
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_PANEL_ADDED,
+ content::Source<Panel>(panel),
+ content::NotificationService::NoDetails());
+
return panel;
}
@@ -166,6 +175,11 @@ void PanelManager::DoRemove(Panel* panel) {
gfx::Rect bounds = (*iter)->GetBounds();
Rearrange(panels_.erase(iter), bounds.right());
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_PANEL_REMOVED,
+ content::Source<Panel>(panel),
+ content::NotificationService::NoDetails());
}
void PanelManager::StartDragging(Panel* panel) {
diff --git a/chrome/browser/ui/panels/panel_manager.h b/chrome/browser/ui/panels/panel_manager.h
index 7b272ab6c..0e0d3cb 100644
--- a/chrome/browser/ui/panels/panel_manager.h
+++ b/chrome/browser/ui/panels/panel_manager.h
@@ -73,6 +73,7 @@ class PanelManager : public PanelMouseWatcher::Observer,
int num_panels() const { return panels_.size(); }
bool is_dragging_panel() const;
+ const Panels& panels() const { return panels_; }
int GetMaxPanelWidth() const;
int GetMaxPanelHeight() const;
@@ -82,7 +83,6 @@ class PanelManager : public PanelMouseWatcher::Observer,
virtual void OnMouseMove(const gfx::Point& mouse_position) OVERRIDE;
#ifdef UNIT_TEST
- const Panels& panels() const { return panels_; }
static int horizontal_spacing() { return kPanelsHorizontalSpacing; }
const gfx::Rect& work_area() const {
diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h
index 12477c3..e2467d8 100644
--- a/chrome/common/chrome_notification_types.h
+++ b/chrome/common/chrome_notification_types.h
@@ -947,6 +947,21 @@ enum NotificationType {
// Used only in unit testing.
NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN,
+ // Sent when panel's bounds get changed.
+ // The source is the Panel, no details.
+ // Used only in coordination with notification balloons.
+ NOTIFICATION_PANEL_CHANGED_BOUNDS,
+
+ // Sent when panel is added into the panel manager.
+ // The source is the Panel, no details.
+ // Used only in coordination with notification balloons.
+ NOTIFICATION_PANEL_ADDED,
+
+ // Sent when panel is removed from the panel manager.
+ // The source is the Panel, no details.
+ // Used only in coordination with notification balloons.
+ NOTIFICATION_PANEL_REMOVED,
+
// Sent when a global error has changed and the error UI should update it
// self. The source is a Source<Profile> containing the profile for the
// error. The detail is a GlobalError object that has changed or NULL if