Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/message_center/views/message_popup_collection.h"
      6 
      7 #include <set>
      8 
      9 #include "base/bind.h"
     10 #include "base/i18n/rtl.h"
     11 #include "base/logging.h"
     12 #include "base/memory/weak_ptr.h"
     13 #include "base/run_loop.h"
     14 #include "base/time/time.h"
     15 #include "base/timer/timer.h"
     16 #include "ui/base/accessibility/accessibility_types.h"
     17 #include "ui/gfx/animation/animation_delegate.h"
     18 #include "ui/gfx/animation/slide_animation.h"
     19 #include "ui/gfx/screen.h"
     20 #include "ui/message_center/message_center.h"
     21 #include "ui/message_center/message_center_style.h"
     22 #include "ui/message_center/message_center_tray.h"
     23 #include "ui/message_center/message_center_util.h"
     24 #include "ui/message_center/notification.h"
     25 #include "ui/message_center/notification_list.h"
     26 #include "ui/message_center/views/notification_view.h"
     27 #include "ui/message_center/views/toast_contents_view.h"
     28 #include "ui/views/background.h"
     29 #include "ui/views/layout/fill_layout.h"
     30 #include "ui/views/view.h"
     31 #include "ui/views/views_delegate.h"
     32 #include "ui/views/widget/widget.h"
     33 #include "ui/views/widget/widget_delegate.h"
     34 
     35 namespace message_center {
     36 namespace {
     37 
     38 // Timeout between the last user-initiated close of the toast and the moment
     39 // when normal layout/update of the toast stack continues. If the last toast was
     40 // just closed, the timeout is shorter.
     41 const int kMouseExitedDeferTimeoutMs = 200;
     42 
     43 // The margin between messages (and between the anchor unless
     44 // first_item_has_no_margin was specified).
     45 const int kToastMarginY = kMarginBetweenItems;
     46 #if defined(OS_CHROMEOS)
     47 const int kToastMarginX = 3;
     48 #else
     49 const int kToastMarginX = kMarginBetweenItems;
     50 #endif
     51 
     52 
     53 // If there should be no margin for the first item, this value needs to be
     54 // substracted to flush the message to the shelf (the width of the border +
     55 // shadow).
     56 const int kNoToastMarginBorderAndShadowOffset = 2;
     57 
     58 }  // namespace.
     59 
     60 MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent,
     61                                                MessageCenter* message_center,
     62                                                MessageCenterTray* tray,
     63                                                bool first_item_has_no_margin)
     64     : parent_(parent),
     65       message_center_(message_center),
     66       tray_(tray),
     67       defer_counter_(0),
     68       latest_toast_entered_(NULL),
     69       user_is_closing_toasts_by_clicking_(false),
     70       first_item_has_no_margin_(first_item_has_no_margin),
     71       weak_factory_(this) {
     72   DCHECK(message_center_);
     73   defer_timer_.reset(new base::OneShotTimer<MessagePopupCollection>);
     74   message_center_->AddObserver(this);
     75   gfx::Screen* screen = NULL;
     76   gfx::Display display;
     77   if (!parent_) {
     78     // On Win+Aura, we don't have a parent since the popups currently show up
     79     // on the Windows desktop, not in the Aura/Ash desktop.  This code will
     80     // display the popups on the primary display.
     81     screen = gfx::Screen::GetNativeScreen();
     82     display = screen->GetPrimaryDisplay();
     83   } else {
     84     screen = gfx::Screen::GetScreenFor(parent_);
     85     display = screen->GetDisplayNearestWindow(parent_);
     86   }
     87   screen->AddObserver(this);
     88 
     89   display_id_ = display.id();
     90   work_area_ = display.work_area();
     91   ComputePopupAlignment(work_area_, display.bounds());
     92 
     93   // We should not update before work area and popup alignment are computed.
     94   DoUpdateIfPossible();
     95 }
     96 
     97 MessagePopupCollection::~MessagePopupCollection() {
     98   weak_factory_.InvalidateWeakPtrs();
     99 
    100   gfx::Screen* screen = parent_ ?
    101       gfx::Screen::GetScreenFor(parent_) : gfx::Screen::GetNativeScreen();
    102   screen->RemoveObserver(this);
    103   message_center_->RemoveObserver(this);
    104 
    105   CloseAllWidgets();
    106 }
    107 
    108 void MessagePopupCollection::ClickOnNotification(
    109     const std::string& notification_id) {
    110   message_center_->ClickOnNotification(notification_id);
    111 }
    112 
    113 void MessagePopupCollection::RemoveNotification(
    114     const std::string& notification_id,
    115     bool by_user) {
    116   message_center_->RemoveNotification(notification_id, by_user);
    117 }
    118 
    119 void MessagePopupCollection::DisableNotificationsFromThisSource(
    120     const NotifierId& notifier_id) {
    121   message_center_->DisableNotificationsByNotifier(notifier_id);
    122 }
    123 
    124 void MessagePopupCollection::ShowNotifierSettingsBubble() {
    125   tray_->ShowNotifierSettingsBubble();
    126 }
    127 
    128 bool MessagePopupCollection::HasClickedListener(
    129     const std::string& notification_id) {
    130   return message_center_->HasClickedListener(notification_id);
    131 }
    132 
    133 void MessagePopupCollection::ClickOnNotificationButton(
    134     const std::string& notification_id,
    135     int button_index) {
    136   message_center_->ClickOnNotificationButton(notification_id, button_index);
    137 }
    138 
    139 void MessagePopupCollection::ExpandNotification(
    140     const std::string& notification_id) {
    141   message_center_->ExpandNotification(notification_id);
    142 }
    143 
    144 void MessagePopupCollection::GroupBodyClicked(
    145     const std::string& last_notification_id) {
    146   // No group views in popup collection.
    147   NOTREACHED();
    148 }
    149 
    150 // When clicked on the "N more" button, perform some reasonable action.
    151 // TODO(dimich): find out what the reasonable action could be.
    152 void MessagePopupCollection::ExpandGroup(const NotifierId& notifier_id) {
    153   // No group views in popup collection.
    154   NOTREACHED();
    155 }
    156 
    157 void MessagePopupCollection::RemoveGroup(const NotifierId& notifier_id) {
    158   // No group views in popup collection.
    159   NOTREACHED();
    160 }
    161 
    162 void MessagePopupCollection::MarkAllPopupsShown() {
    163   std::set<std::string> closed_ids = CloseAllWidgets();
    164   for (std::set<std::string>::iterator iter = closed_ids.begin();
    165        iter != closed_ids.end(); iter++) {
    166     message_center_->MarkSinglePopupAsShown(*iter, false);
    167   }
    168 }
    169 
    170 void MessagePopupCollection::UpdateWidgets() {
    171   NotificationList::PopupNotifications popups =
    172       message_center_->GetPopupNotifications();
    173 
    174   if (popups.empty()) {
    175     CloseAllWidgets();
    176     return;
    177   }
    178 
    179   bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
    180   int base = GetBaseLine(toasts_.empty() ? NULL : toasts_.back());
    181 
    182   // Iterate in the reverse order to keep the oldest toasts on screen. Newer
    183   // items may be ignored if there are no room to place them.
    184   for (NotificationList::PopupNotifications::const_reverse_iterator iter =
    185            popups.rbegin(); iter != popups.rend(); ++iter) {
    186     if (FindToast((*iter)->id()))
    187       continue;
    188 
    189     bool expanded = true;
    190     if (IsExperimentalNotificationUIEnabled())
    191       expanded = (*iter)->is_expanded();
    192     NotificationView* view =
    193         NotificationView::Create(NULL,
    194                                  *(*iter),
    195                                  expanded,
    196                                  true); // Create top-level notification.
    197     int view_height = ToastContentsView::GetToastSizeForView(view).height();
    198     int height_available = top_down ? work_area_.bottom() - base : base;
    199 
    200     if (height_available - view_height - kToastMarginY < 0) {
    201       delete view;
    202       break;
    203     }
    204 
    205     ToastContentsView* toast =
    206         new ToastContentsView((*iter)->id(), weak_factory_.GetWeakPtr());
    207     // There will be no contents already since this is a new ToastContentsView.
    208     toast->SetContents(view, /*a11y_feedback_for_updates=*/false);
    209     toasts_.push_back(toast);
    210     view->set_controller(toast);
    211 
    212     gfx::Size preferred_size = toast->GetPreferredSize();
    213     gfx::Point origin(GetToastOriginX(gfx::Rect(preferred_size)), base);
    214     // The toast slides in from the edge of the screen horizontally.
    215     if (alignment_ & POPUP_ALIGNMENT_LEFT)
    216       origin.set_x(origin.x() - preferred_size.width());
    217     else
    218       origin.set_x(origin.x() + preferred_size.width());
    219     if (top_down)
    220       origin.set_y(origin.y() + view_height);
    221 
    222     toast->RevealWithAnimation(origin);
    223 
    224     // Shift the base line to be a few pixels above the last added toast or (few
    225     // pixels below last added toast if top-aligned).
    226     if (top_down)
    227       base += view_height + kToastMarginY;
    228     else
    229       base -= view_height + kToastMarginY;
    230 
    231     if (views::ViewsDelegate::views_delegate) {
    232       views::ViewsDelegate::views_delegate->NotifyAccessibilityEvent(
    233           toast, ui::AccessibilityTypes::EVENT_ALERT);
    234     }
    235 
    236     message_center_->DisplayedNotification((*iter)->id());
    237   }
    238 }
    239 
    240 void MessagePopupCollection::OnMouseEntered(ToastContentsView* toast_entered) {
    241   // Sometimes we can get two MouseEntered/MouseExited in a row when animating
    242   // toasts.  So we need to keep track of which one is the currently active one.
    243   latest_toast_entered_ = toast_entered;
    244 
    245   message_center_->PausePopupTimers();
    246 
    247   if (user_is_closing_toasts_by_clicking_)
    248     defer_timer_->Stop();
    249 }
    250 
    251 void MessagePopupCollection::OnMouseExited(ToastContentsView* toast_exited) {
    252   // If we're exiting a toast after entering a different toast, then ignore
    253   // this mouse event.
    254   if (toast_exited != latest_toast_entered_)
    255     return;
    256   latest_toast_entered_ = NULL;
    257 
    258   if (user_is_closing_toasts_by_clicking_) {
    259     defer_timer_->Start(
    260         FROM_HERE,
    261         base::TimeDelta::FromMilliseconds(kMouseExitedDeferTimeoutMs),
    262         this,
    263         &MessagePopupCollection::OnDeferTimerExpired);
    264   } else {
    265     message_center_->RestartPopupTimers();
    266   }
    267 }
    268 
    269 std::set<std::string> MessagePopupCollection::CloseAllWidgets() {
    270   std::set<std::string> closed_toast_ids;
    271 
    272   while (!toasts_.empty()) {
    273     ToastContentsView* toast = toasts_.front();
    274     toasts_.pop_front();
    275     closed_toast_ids.insert(toast->id());
    276 
    277     OnMouseExited(toast);
    278 
    279     // CloseWithAnimation will cause the toast to forget about |this| so it is
    280     // required when we forget a toast.
    281     toast->CloseWithAnimation();
    282   }
    283 
    284   return closed_toast_ids;
    285 }
    286 
    287 void MessagePopupCollection::ForgetToast(ToastContentsView* toast) {
    288   toasts_.remove(toast);
    289   OnMouseExited(toast);
    290 }
    291 
    292 void MessagePopupCollection::RemoveToast(ToastContentsView* toast,
    293                                          bool mark_as_shown) {
    294   ForgetToast(toast);
    295 
    296   toast->CloseWithAnimation();
    297 
    298   if (mark_as_shown)
    299     message_center_->MarkSinglePopupAsShown(toast->id(), false);
    300 }
    301 
    302 int MessagePopupCollection::GetToastOriginX(const gfx::Rect& toast_bounds)
    303     const {
    304 #if defined(OS_CHROMEOS)
    305   // In ChromeOS, RTL UI language mirrors the whole desktop layout, so the toast
    306   // widgets should be at the bottom-left instead of bottom right.
    307   if (base::i18n::IsRTL())
    308     return work_area_.x() + kToastMarginX;
    309 #endif
    310   if (alignment_ & POPUP_ALIGNMENT_LEFT)
    311     return work_area_.x() + kToastMarginX;
    312   return work_area_.right() - kToastMarginX - toast_bounds.width();
    313 }
    314 
    315 void MessagePopupCollection::RepositionWidgets() {
    316   bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
    317   int base = GetBaseLine(NULL);  // We don't want to position relative to last
    318                                  // toast - we want re-position.
    319 
    320   for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();) {
    321     Toasts::const_iterator curr = iter++;
    322     gfx::Rect bounds((*curr)->bounds());
    323     bounds.set_x(GetToastOriginX(bounds));
    324     bounds.set_y(alignment_ & POPUP_ALIGNMENT_TOP ? base
    325                                                   : base - bounds.height());
    326 
    327     // The notification may scrolls the boundary of the screen due to image
    328     // load and such notifications should disappear. Do not call
    329     // CloseWithAnimation, we don't want to show the closing animation, and we
    330     // don't want to mark such notifications as shown. See crbug.com/233424
    331     if ((top_down ? work_area_.bottom() - bounds.bottom() : bounds.y()) >= 0)
    332       (*curr)->SetBoundsWithAnimation(bounds);
    333     else
    334       RemoveToast(*curr, /*mark_as_shown=*/false);
    335 
    336     // Shift the base line to be a few pixels above the last added toast or (few
    337     // pixels below last added toast if top-aligned).
    338     if (top_down)
    339       base += bounds.height() + kToastMarginY;
    340     else
    341       base -= bounds.height() + kToastMarginY;
    342   }
    343 }
    344 
    345 void MessagePopupCollection::RepositionWidgetsWithTarget() {
    346   if (toasts_.empty())
    347     return;
    348 
    349   bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
    350 
    351   // Nothing to do if there are no widgets above target if bottom-aligned or no
    352   // widgets below target if top-aligned.
    353   if (top_down ? toasts_.back()->origin().y() < target_top_edge_
    354                : toasts_.back()->origin().y() > target_top_edge_)
    355     return;
    356 
    357   Toasts::reverse_iterator iter = toasts_.rbegin();
    358   for (; iter != toasts_.rend(); ++iter) {
    359     // We only reposition widgets above target if bottom-aligned or widgets
    360     // below target if top-aligned.
    361     if (top_down ? (*iter)->origin().y() < target_top_edge_
    362                  : (*iter)->origin().y() > target_top_edge_)
    363       break;
    364   }
    365   --iter;
    366 
    367   // Slide length is the number of pixels the widgets should move so that their
    368   // bottom edge (top-edge if top-aligned) touches the target.
    369   int slide_length = std::abs(target_top_edge_ - (*iter)->origin().y());
    370   for (;; --iter) {
    371     gfx::Rect bounds((*iter)->bounds());
    372 
    373     // If top-aligned, shift widgets upwards by slide_length. If bottom-aligned,
    374     // shift them downwards by slide_length.
    375     if (top_down)
    376       bounds.set_y(bounds.y() - slide_length);
    377     else
    378       bounds.set_y(bounds.y() + slide_length);
    379     (*iter)->SetBoundsWithAnimation(bounds);
    380 
    381     if (iter == toasts_.rbegin())
    382       break;
    383   }
    384 }
    385 
    386 void MessagePopupCollection::ComputePopupAlignment(gfx::Rect work_area,
    387                                                    gfx::Rect screen_bounds) {
    388   // If the taskbar is at the top, render notifications top down. Some platforms
    389   // like Gnome can have taskbars at top and bottom. In this case it's more
    390   // likely that the systray is on the top one.
    391   alignment_ = work_area.y() > screen_bounds.y() ? POPUP_ALIGNMENT_TOP
    392                                                  : POPUP_ALIGNMENT_BOTTOM;
    393 
    394   // If the taskbar is on the left show the notifications on the left. Otherwise
    395   // show it on right since it's very likely that the systray is on the right if
    396   // the taskbar is on the top or bottom.
    397   // Since on some platforms like Ubuntu Unity there's also a launcher along
    398   // with a taskbar (panel), we need to check that there is really nothing at
    399   // the top before concluding that the taskbar is at the left.
    400   alignment_ = static_cast<PopupAlignment>(
    401       alignment_ |
    402       ((work_area.x() > screen_bounds.x() && work_area.y() == screen_bounds.y())
    403            ? POPUP_ALIGNMENT_LEFT
    404            : POPUP_ALIGNMENT_RIGHT));
    405 }
    406 
    407 int MessagePopupCollection::GetBaseLine(ToastContentsView* last_toast) const {
    408   bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
    409   int base;
    410 
    411   if (top_down) {
    412     if (!last_toast) {
    413       base = work_area_.y();
    414       if (!first_item_has_no_margin_)
    415         base += kToastMarginY;
    416       else
    417         base -= kNoToastMarginBorderAndShadowOffset;
    418     } else {
    419       base = toasts_.back()->bounds().bottom() + kToastMarginY;
    420     }
    421   } else {
    422     if (!last_toast) {
    423       base = work_area_.bottom();
    424       if (!first_item_has_no_margin_)
    425         base -= kToastMarginY;
    426       else
    427         base += kNoToastMarginBorderAndShadowOffset;
    428     } else {
    429       base = toasts_.back()->origin().y() - kToastMarginY;
    430     }
    431   }
    432   return base;
    433 }
    434 
    435 void MessagePopupCollection::OnNotificationAdded(
    436     const std::string& notification_id) {
    437   DoUpdateIfPossible();
    438 }
    439 
    440 void MessagePopupCollection::OnNotificationRemoved(
    441     const std::string& notification_id,
    442     bool by_user) {
    443   // Find a toast.
    444   Toasts::const_iterator iter = toasts_.begin();
    445   for (; iter != toasts_.end(); ++iter) {
    446     if ((*iter)->id() == notification_id)
    447       break;
    448   }
    449   if (iter == toasts_.end())
    450     return;
    451 
    452   target_top_edge_ = (*iter)->bounds().y();
    453   if (by_user && !user_is_closing_toasts_by_clicking_) {
    454     // [Re] start a timeout after which the toasts re-position to their
    455     // normal locations after tracking the mouse pointer for easy deletion.
    456     // This provides a period of time when toasts are easy to remove because
    457     // they re-position themselves to have Close button right under the mouse
    458     // pointer. If the user continue to remove the toasts, the delay is reset.
    459     // Once user stopped removing the toasts, the toasts re-populate/rearrange
    460     // after the specified delay.
    461     user_is_closing_toasts_by_clicking_ = true;
    462     IncrementDeferCounter();
    463   }
    464 
    465   // CloseWithAnimation ultimately causes a call to RemoveToast, which calls
    466   // OnMouseExited.  This means that |user_is_closing_toasts_by_clicking_| must
    467   // have been set before this call, otherwise it will remain true even after
    468   // the toast is closed, since the defer timer won't be started.
    469   RemoveToast(*iter, /*mark_as_shown=*/true);
    470 
    471   if (by_user)
    472     RepositionWidgetsWithTarget();
    473 }
    474 
    475 void MessagePopupCollection::OnDeferTimerExpired() {
    476   user_is_closing_toasts_by_clicking_ = false;
    477   DecrementDeferCounter();
    478 
    479   message_center_->RestartPopupTimers();
    480 }
    481 
    482 void MessagePopupCollection::OnNotificationUpdated(
    483     const std::string& notification_id) {
    484   // Find a toast.
    485   Toasts::const_iterator toast_iter = toasts_.begin();
    486   for (; toast_iter != toasts_.end(); ++toast_iter) {
    487     if ((*toast_iter)->id() == notification_id)
    488       break;
    489   }
    490   if (toast_iter == toasts_.end())
    491     return;
    492 
    493   NotificationList::PopupNotifications notifications =
    494       message_center_->GetPopupNotifications();
    495   bool updated = false;
    496 
    497   for (NotificationList::PopupNotifications::iterator iter =
    498            notifications.begin(); iter != notifications.end(); ++iter) {
    499     if ((*iter)->id() != notification_id)
    500       continue;
    501 
    502     bool expanded = true;
    503     if (IsExperimentalNotificationUIEnabled())
    504       expanded = (*iter)->is_expanded();
    505 
    506     const RichNotificationData& optional_fields =
    507         (*iter)->rich_notification_data();
    508     bool a11y_feedback_for_updates =
    509         optional_fields.should_make_spoken_feedback_for_popup_updates;
    510 
    511     NotificationView* view =
    512         NotificationView::Create(*toast_iter,
    513                                  *(*iter),
    514                                  expanded,
    515                                  true); // Create top-level notification.
    516     (*toast_iter)->SetContents(view, a11y_feedback_for_updates);
    517     updated = true;
    518   }
    519 
    520   // OnNotificationUpdated() can be called when a notification is excluded from
    521   // the popup notification list but still remains in the full notification
    522   // list. In that case the widget for the notification has to be closed here.
    523   if (!updated)
    524     RemoveToast(*toast_iter, /*mark_as_shown=*/true);
    525 
    526   if (user_is_closing_toasts_by_clicking_)
    527     RepositionWidgetsWithTarget();
    528   else
    529     DoUpdateIfPossible();
    530 }
    531 
    532 ToastContentsView* MessagePopupCollection::FindToast(
    533     const std::string& notification_id) const {
    534   for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
    535        ++iter) {
    536     if ((*iter)->id() == notification_id)
    537       return *iter;
    538   }
    539   return NULL;
    540 }
    541 
    542 void MessagePopupCollection::IncrementDeferCounter() {
    543   defer_counter_++;
    544 }
    545 
    546 void MessagePopupCollection::DecrementDeferCounter() {
    547   defer_counter_--;
    548   DCHECK(defer_counter_ >= 0);
    549   DoUpdateIfPossible();
    550 }
    551 
    552 // This is the main sequencer of tasks. It does a step, then waits for
    553 // all started transitions to play out before doing the next step.
    554 // First, remove all expired toasts.
    555 // Then, reposition widgets (the reposition on close happens before all
    556 // deferred tasks are even able to run)
    557 // Then, see if there is vacant space for new toasts.
    558 void MessagePopupCollection::DoUpdateIfPossible() {
    559   if (defer_counter_ > 0)
    560     return;
    561 
    562   RepositionWidgets();
    563 
    564   if (defer_counter_ > 0)
    565     return;
    566 
    567   // Reposition could create extra space which allows additional widgets.
    568   UpdateWidgets();
    569 
    570   if (defer_counter_ > 0)
    571     return;
    572 
    573   // Test support. Quit the test run loop when no more updates are deferred,
    574   // meaining th echeck for updates did not cause anything to change so no new
    575   // transition animations were started.
    576   if (run_loop_for_test_.get())
    577     run_loop_for_test_->Quit();
    578 }
    579 
    580 void MessagePopupCollection::SetDisplayInfo(const gfx::Rect& work_area,
    581                                             const gfx::Rect& screen_bounds) {
    582   if (work_area_ == work_area)
    583     return;
    584 
    585   work_area_ = work_area;
    586   ComputePopupAlignment(work_area, screen_bounds);
    587   RepositionWidgets();
    588 }
    589 
    590 void MessagePopupCollection::OnDisplayBoundsChanged(
    591     const gfx::Display& display) {
    592   if (display.id() != display_id_)
    593     return;
    594 
    595   SetDisplayInfo(display.work_area(), display.bounds());
    596 }
    597 
    598 void MessagePopupCollection::OnDisplayAdded(const gfx::Display& new_display) {
    599 }
    600 
    601 void MessagePopupCollection::OnDisplayRemoved(const gfx::Display& old_display) {
    602 }
    603 
    604 views::Widget* MessagePopupCollection::GetWidgetForTest(const std::string& id)
    605     const {
    606   for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
    607        ++iter) {
    608     if ((*iter)->id() == id)
    609       return (*iter)->GetWidget();
    610   }
    611   return NULL;
    612 }
    613 
    614 void MessagePopupCollection::CreateRunLoopForTest() {
    615   run_loop_for_test_.reset(new base::RunLoop());
    616 }
    617 
    618 void MessagePopupCollection::WaitForTest() {
    619   run_loop_for_test_->Run();
    620   run_loop_for_test_.reset();
    621 }
    622 
    623 gfx::Rect MessagePopupCollection::GetToastRectAt(size_t index) const {
    624   DCHECK(defer_counter_ == 0) << "Fetching the bounds with animations active.";
    625   size_t i = 0;
    626   for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
    627        ++iter) {
    628     if (i++ == index) {
    629       views::Widget* widget = (*iter)->GetWidget();
    630       if (widget)
    631         return widget->GetWindowBoundsInScreen();
    632       break;
    633     }
    634   }
    635   return gfx::Rect();
    636 }
    637 
    638 }  // namespace message_center
    639