Home | History | Annotate | Download | only in panels
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/panels/panel_manager.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/logging.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/ui/panels/detached_panel_collection.h"
     14 #include "chrome/browser/ui/panels/docked_panel_collection.h"
     15 #include "chrome/browser/ui/panels/panel_drag_controller.h"
     16 #include "chrome/browser/ui/panels/panel_mouse_watcher.h"
     17 #include "chrome/browser/ui/panels/panel_resize_controller.h"
     18 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
     19 #include "chrome/common/chrome_switches.h"
     20 #include "chrome/common/chrome_version_info.h"
     21 #include "content/public/browser/notification_service.h"
     22 #include "content/public/browser/notification_source.h"
     23 
     24 #if defined(TOOLKIT_GTK)
     25 #include "base/environment.h"
     26 #include "base/nix/xdg_util.h"
     27 #include "ui/base/x/x11_util.h"
     28 #endif
     29 
     30 #if defined(OS_WIN)
     31 #include "win8/util/win8_util.h"
     32 #endif
     33 
     34 namespace {
     35 // Maxmium width of a panel is based on a factor of the working area.
     36 #if defined(OS_CHROMEOS)
     37 // ChromeOS device screens are relatively small and limiting the width
     38 // interferes with some apps (e.g. http://crbug.com/111121).
     39 const double kPanelMaxWidthFactor = 0.80;
     40 #else
     41 const double kPanelMaxWidthFactor = 0.35;
     42 #endif
     43 
     44 // Maxmium height of a panel is based on a factor of the working area.
     45 const double kPanelMaxHeightFactor = 0.5;
     46 
     47 // Width to height ratio is used to compute the default width or height
     48 // when only one value is provided.
     49 const double kPanelDefaultWidthToHeightRatio = 1.62;  // golden ratio
     50 
     51 // The test code could call PanelManager::SetDisplaySettingsProviderForTesting
     52 // to set this for testing purpose.
     53 DisplaySettingsProvider* display_settings_provider_for_testing;
     54 
     55 // The following comparers are used by std::list<>::sort to determine which
     56 // stack or panel we want to seacrh first for adding new panel.
     57 bool ComparePanelsByPosition(Panel* panel1, Panel* panel2) {
     58   gfx::Rect bounds1 = panel1->GetBounds();
     59   gfx::Rect bounds2 = panel2->GetBounds();
     60 
     61   // When there're ties, the right-most stack will appear first.
     62   if (bounds1.x() > bounds2.x())
     63     return true;
     64   if (bounds1.x() < bounds2.x())
     65     return false;
     66 
     67   // In the event of another draw, the top-most stack will appear first.
     68   return bounds1.y() < bounds2.y();
     69 }
     70 
     71 bool ComparerNumberOfPanelsInStack(StackedPanelCollection* stack1,
     72                                    StackedPanelCollection* stack2) {
     73   // The stack with more panels will appear first.
     74   int num_panels_in_stack1 = stack1->num_panels();
     75   int num_panels_in_stack2 = stack2->num_panels();
     76   if (num_panels_in_stack1 > num_panels_in_stack2)
     77     return true;
     78   if (num_panels_in_stack1 < num_panels_in_stack2)
     79     return false;
     80 
     81   DCHECK(num_panels_in_stack1);
     82 
     83   return ComparePanelsByPosition(stack1->top_panel(), stack2->top_panel());
     84 }
     85 
     86 bool CompareDetachedPanels(Panel* panel1, Panel* panel2) {
     87   return ComparePanelsByPosition(panel1, panel2);
     88 }
     89 
     90 }  // namespace
     91 
     92 // static
     93 bool PanelManager::shorten_time_intervals_ = false;
     94 
     95 // static
     96 PanelManager* PanelManager::GetInstance() {
     97   static base::LazyInstance<PanelManager> instance = LAZY_INSTANCE_INITIALIZER;
     98   return instance.Pointer();
     99 }
    100 
    101 // static
    102 void PanelManager::SetDisplaySettingsProviderForTesting(
    103     DisplaySettingsProvider* provider) {
    104   display_settings_provider_for_testing = provider;
    105 }
    106 
    107 // static
    108 bool PanelManager::ShouldUsePanels(const std::string& extension_id) {
    109 #if defined(TOOLKIT_GTK)
    110   // If --enable-panels is on, always use panels on Linux.
    111   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePanels))
    112     return true;
    113 
    114   // Otherwise, panels are only supported on tested window managers.
    115   ui::WindowManagerName wm_type = ui::GuessWindowManager();
    116   if (wm_type != ui::WM_COMPIZ &&
    117       wm_type != ui::WM_ICE_WM &&
    118       wm_type != ui::WM_KWIN &&
    119       wm_type != ui::WM_METACITY &&
    120       wm_type != ui::WM_MUFFIN &&
    121       wm_type != ui::WM_MUTTER &&
    122       wm_type != ui::WM_XFWM4) {
    123     return false;
    124   }
    125 #endif  // TOOLKIT_GTK
    126 
    127 #if defined(OS_WIN)
    128   // No panels in Metro mode.
    129   if (win8::IsSingleWindowMetroMode())
    130     return false;
    131 #endif // OS_WIN
    132 
    133   chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
    134   if (channel == chrome::VersionInfo::CHANNEL_STABLE ||
    135       channel == chrome::VersionInfo::CHANNEL_BETA) {
    136     return CommandLine::ForCurrentProcess()->HasSwitch(
    137         switches::kEnablePanels) ||
    138         extension_id == std::string("nckgahadagoaajjgafhacjanaoiihapd") ||
    139         extension_id == std::string("ljclpkphhpbpinifbeabbhlfddcpfdde") ||
    140         extension_id == std::string("ppleadejekpmccmnpjdimmlfljlkdfej") ||
    141         extension_id == std::string("eggnbpckecmjlblplehfpjjdhhidfdoj");
    142   }
    143 
    144   return true;
    145 }
    146 
    147 // static
    148 bool PanelManager::IsPanelStackingEnabled() {
    149   return true;
    150 }
    151 
    152 // static
    153 bool PanelManager::CanUseSystemMinimize() {
    154 #if defined(TOOLKIT_GTK)
    155   static base::nix::DesktopEnvironment desktop_env =
    156       base::nix::DESKTOP_ENVIRONMENT_OTHER;
    157   if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_OTHER) {
    158     scoped_ptr<base::Environment> env(base::Environment::Create());
    159     desktop_env = base::nix::GetDesktopEnvironment(env.get());
    160   }
    161   return desktop_env != base::nix::DESKTOP_ENVIRONMENT_UNITY;
    162 #else
    163   return true;
    164 #endif
    165 }
    166 
    167 PanelManager::PanelManager()
    168     : panel_mouse_watcher_(PanelMouseWatcher::Create()),
    169       auto_sizing_enabled_(true) {
    170   // DisplaySettingsProvider should be created before the creation of
    171   // collections since some collection might depend on it.
    172   if (display_settings_provider_for_testing)
    173     display_settings_provider_.reset(display_settings_provider_for_testing);
    174   else
    175     display_settings_provider_.reset(DisplaySettingsProvider::Create());
    176   display_settings_provider_->AddDisplayObserver(this);
    177 
    178   detached_collection_.reset(new DetachedPanelCollection(this));
    179   docked_collection_.reset(new DockedPanelCollection(this));
    180   drag_controller_.reset(new PanelDragController(this));
    181   resize_controller_.reset(new PanelResizeController(this));
    182 }
    183 
    184 PanelManager::~PanelManager() {
    185   display_settings_provider_->RemoveDisplayObserver(this);
    186 
    187   // Docked collection should be disposed explicitly before
    188   // DisplaySettingsProvider is gone since docked collection needs to remove
    189   // the observer from DisplaySettingsProvider.
    190   docked_collection_.reset();
    191 }
    192 
    193 gfx::Point PanelManager::GetDefaultDetachedPanelOrigin() {
    194   return detached_collection_->GetDefaultPanelOrigin();
    195 }
    196 
    197 void PanelManager::OnDisplayChanged() {
    198   docked_collection_->OnDisplayChanged();
    199   detached_collection_->OnDisplayChanged();
    200   for (Stacks::const_iterator iter = stacks_.begin();
    201        iter != stacks_.end(); iter++)
    202     (*iter)->OnDisplayChanged();
    203 }
    204 
    205 void PanelManager::OnFullScreenModeChanged(bool is_full_screen) {
    206   std::vector<Panel*> all_panels = panels();
    207   for (std::vector<Panel*>::const_iterator iter = all_panels.begin();
    208        iter != all_panels.end(); ++iter) {
    209     Panel* panel = *iter;
    210     PanelCollection* panel_collection = panel->collection();
    211     // When the panel is not always on top, there is no need to hide/show
    212     // the panel in response to entering/leaving full-screen mode.
    213     if (panel_collection && panel_collection->UsesAlwaysOnTopPanels())
    214       panel->FullScreenModeChanged(is_full_screen);
    215   }
    216 }
    217 
    218 int PanelManager::GetMaxPanelWidth(const gfx::Rect& work_area) const {
    219   return static_cast<int>(work_area.width() * kPanelMaxWidthFactor);
    220 }
    221 
    222 int PanelManager::GetMaxPanelHeight(const gfx::Rect& work_area) const {
    223   return static_cast<int>(work_area.height() * kPanelMaxHeightFactor);
    224 }
    225 
    226 Panel* PanelManager::CreatePanel(const std::string& app_name,
    227                                  Profile* profile,
    228                                  const GURL& url,
    229                                  const gfx::Rect& requested_bounds,
    230                                  CreateMode mode) {
    231   // Need to sync the display area if no panel is present. This is because:
    232   // 1) Display area is not initialized until first panel is created.
    233   // 2) On windows, display settings notification is tied to a window. When
    234   //    display settings are changed at the time that no panel exists, we do
    235   //    not receive any notification.
    236   if (num_panels() == 0) {
    237     display_settings_provider_->OnDisplaySettingsChanged();
    238     display_settings_provider_->AddFullScreenObserver(this);
    239   }
    240 
    241   // Compute initial bounds for the panel.
    242   int width = requested_bounds.width();
    243   int height = requested_bounds.height();
    244   if (width == 0)
    245     width = height * kPanelDefaultWidthToHeightRatio;
    246   else if (height == 0)
    247     height = width / kPanelDefaultWidthToHeightRatio;
    248 
    249   gfx::Rect work_area =
    250       display_settings_provider_->GetWorkAreaMatching(requested_bounds);
    251   gfx::Size min_size(panel::kPanelMinWidth, panel::kPanelMinHeight);
    252   gfx::Size max_size(GetMaxPanelWidth(work_area), GetMaxPanelHeight(work_area));
    253   if (width < min_size.width())
    254     width = min_size.width();
    255   else if (width > max_size.width())
    256     width = max_size.width();
    257 
    258   if (height < min_size.height())
    259     height = min_size.height();
    260   else if (height > max_size.height())
    261     height = max_size.height();
    262 
    263   // Create the panel.
    264   Panel* panel = new Panel(profile, app_name, min_size, max_size);
    265 
    266   // Find the appropriate panel collection to hold the new panel.
    267   gfx::Rect adjusted_requested_bounds(
    268       requested_bounds.x(), requested_bounds.y(), width, height);
    269   PanelCollection::PositioningMask positioning_mask;
    270   PanelCollection* collection = GetCollectionForNewPanel(
    271       panel, adjusted_requested_bounds, mode, &positioning_mask);
    272 
    273   // Let the panel collection decide the initial bounds.
    274   gfx::Rect bounds = collection->GetInitialPanelBounds(
    275       adjusted_requested_bounds);
    276   bounds.AdjustToFit(work_area);
    277 
    278   panel->Initialize(url, bounds, collection->UsesAlwaysOnTopPanels());
    279 
    280   // Auto resizable feature is enabled only if no initial size is requested.
    281   if (auto_sizing_enabled() && requested_bounds.width() == 0 &&
    282       requested_bounds.height() == 0) {
    283     panel->SetAutoResizable(true);
    284   }
    285 
    286   // Add the panel to the panel collection.
    287   collection->AddPanel(panel, positioning_mask);
    288   collection->UpdatePanelOnCollectionChange(panel);
    289 
    290   return panel;
    291 }
    292 
    293 PanelCollection* PanelManager::GetCollectionForNewPanel(
    294     Panel* new_panel,
    295     const gfx::Rect& bounds,
    296     CreateMode mode,
    297     PanelCollection::PositioningMask* positioning_mask) {
    298   if (mode == CREATE_AS_DOCKED) {
    299     // Delay layout refreshes in case multiple panels are created within
    300     // a short time of one another or the focus changes shortly after panel
    301     // is created to avoid excessive screen redraws.
    302     *positioning_mask = PanelCollection::DELAY_LAYOUT_REFRESH;
    303     return docked_collection_.get();
    304   }
    305 
    306   DCHECK_EQ(CREATE_AS_DETACHED, mode);
    307   *positioning_mask = PanelCollection::DEFAULT_POSITION;
    308 
    309   // If the stacking support is not enabled, new panel will still be created as
    310   // detached.
    311   if (!IsPanelStackingEnabled())
    312     return detached_collection_.get();
    313 
    314   // If there're stacks, try to find a stack that can fit new panel.
    315   if (!stacks_.empty()) {
    316     // Perform the search as:
    317     // 1) Search from the stack with more panels to the stack with least panels.
    318     // 2) Amongs the stacks with same number of panels, search from the right-
    319     //    most stack to the left-most stack.
    320     // 3) Among the stack with same number of panels and same x position,
    321     //    search from the top-most stack to the bottom-most stack.
    322     // 4) If there is not enough space to fit new panel even with all inactive
    323     //    panels being collapsed, move to next stack.
    324     stacks_.sort(ComparerNumberOfPanelsInStack);
    325     for (Stacks::const_iterator iter = stacks_.begin();
    326          iter != stacks_.end(); iter++) {
    327       StackedPanelCollection* stack = *iter;
    328 
    329       // Do not add to other stack that is from differnt extension or profile.
    330       // Note that the check is based on bottom panel.
    331       Panel* panel = stack->bottom_panel();
    332       if (panel->profile() != new_panel->profile() ||
    333           panel->extension_id() != new_panel->extension_id())
    334         continue;
    335 
    336       // Do not add to the stack that is minimized by the system.
    337       if (stack->IsMinimized())
    338         continue;
    339 
    340       // Do not stack with the panel that is not shown in current virtual
    341       // desktop.
    342       if (!panel->IsShownOnActiveDesktop())
    343         continue;
    344 
    345       if (bounds.height() <= stack->GetMaximiumAvailableBottomSpace()) {
    346         *positioning_mask = static_cast<PanelCollection::PositioningMask>(
    347             *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
    348         return stack;
    349       }
    350     }
    351   }
    352 
    353   // Then try to find a detached panel to which new panel can stack.
    354   if (detached_collection_->num_panels()) {
    355     // Perform the search as:
    356     // 1) Search from the right-most detached panel to the left-most detached
    357     //    panel.
    358     // 2) Among the detached panels with same x position, search from the
    359     //    top-most detached panel to the bottom-most deatched panel.
    360     // 3) If there is not enough space beneath the detached panel, even by
    361     //    collapsing it if it is inactive, to fit new panel, move to next
    362     //    detached panel.
    363     detached_collection_->SortPanels(CompareDetachedPanels);
    364 
    365     for (DetachedPanelCollection::Panels::const_iterator iter =
    366              detached_collection_->panels().begin();
    367          iter != detached_collection_->panels().end(); ++iter) {
    368       Panel* panel = *iter;
    369 
    370       // Do not stack with other panel that is from differnt extension or
    371       // profile.
    372       if (panel->profile() != new_panel->profile() ||
    373           panel->extension_id() != new_panel->extension_id())
    374         continue;
    375 
    376       // Do not stack with the panel that is minimized by the system.
    377       if (panel->IsMinimizedBySystem())
    378         continue;
    379 
    380       // Do not stack with the panel that is not shown in the active desktop.
    381       if (!panel->IsShownOnActiveDesktop())
    382         continue;
    383 
    384       gfx::Rect work_area =
    385           display_settings_provider_->GetWorkAreaMatching(panel->GetBounds());
    386       int max_available_space =
    387           work_area.bottom() - panel->GetBounds().y() -
    388           (panel->IsActive() ? panel->GetBounds().height()
    389                              : panel::kTitlebarHeight);
    390       if (bounds.height() <= max_available_space) {
    391         StackedPanelCollection* new_stack = CreateStack();
    392         MovePanelToCollection(panel,
    393                               new_stack,
    394                               PanelCollection::DEFAULT_POSITION);
    395         *positioning_mask = static_cast<PanelCollection::PositioningMask>(
    396             *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
    397         return new_stack;
    398       }
    399     }
    400   }
    401 
    402   return detached_collection_.get();
    403 }
    404 
    405 void PanelManager::OnPanelClosed(Panel* panel) {
    406   if (num_panels() == 1) {
    407     display_settings_provider_->RemoveFullScreenObserver(this);
    408   }
    409 
    410   drag_controller_->OnPanelClosed(panel);
    411   resize_controller_->OnPanelClosed(panel);
    412 
    413   // Note that we need to keep track of panel's collection since it will be
    414   // gone once RemovePanel is called.
    415   PanelCollection* collection = panel->collection();
    416   collection->RemovePanel(panel, PanelCollection::PANEL_CLOSED);
    417 
    418   // If only one panel is left in the stack, move it out of the stack.
    419   // Also make sure that this detached panel will be expanded if not yet.
    420   if (collection->type() == PanelCollection::STACKED) {
    421     StackedPanelCollection* stack =
    422         static_cast<StackedPanelCollection*>(collection);
    423     DCHECK_GE(stack->num_panels(), 1);
    424     if (stack->num_panels() == 1) {
    425       Panel* top_panel = stack->top_panel();
    426       MovePanelToCollection(top_panel,
    427                             detached_collection(),
    428                             PanelCollection::DEFAULT_POSITION);
    429       if (top_panel->expansion_state() != Panel::EXPANDED)
    430         top_panel->SetExpansionState(Panel::EXPANDED);
    431       RemoveStack(stack);
    432     }
    433   }
    434 
    435   content::NotificationService::current()->Notify(
    436       chrome::NOTIFICATION_PANEL_CLOSED,
    437       content::Source<Panel>(panel),
    438       content::NotificationService::NoDetails());
    439 }
    440 
    441 StackedPanelCollection* PanelManager::CreateStack() {
    442   StackedPanelCollection* stack = new StackedPanelCollection(this);
    443   stacks_.push_back(stack);
    444   return stack;
    445 }
    446 
    447 void PanelManager::RemoveStack(StackedPanelCollection* stack) {
    448   DCHECK_EQ(0, stack->num_panels());
    449   stacks_.remove(stack);
    450   stack->CloseAll();
    451   delete stack;
    452 }
    453 
    454 void PanelManager::StartDragging(Panel* panel,
    455                                  const gfx::Point& mouse_location) {
    456   drag_controller_->StartDragging(panel, mouse_location);
    457 }
    458 
    459 void PanelManager::Drag(const gfx::Point& mouse_location) {
    460   drag_controller_->Drag(mouse_location);
    461 }
    462 
    463 void PanelManager::EndDragging(bool cancelled) {
    464   drag_controller_->EndDragging(cancelled);
    465 }
    466 
    467 void PanelManager::StartResizingByMouse(Panel* panel,
    468     const gfx::Point& mouse_location,
    469     panel::ResizingSides sides) {
    470   if (panel->CanResizeByMouse() != panel::NOT_RESIZABLE &&
    471       sides != panel::RESIZE_NONE)
    472     resize_controller_->StartResizing(panel, mouse_location, sides);
    473 }
    474 
    475 void PanelManager::ResizeByMouse(const gfx::Point& mouse_location) {
    476   if (resize_controller_->IsResizing())
    477     resize_controller_->Resize(mouse_location);
    478 }
    479 
    480 void PanelManager::EndResizingByMouse(bool cancelled) {
    481   if (resize_controller_->IsResizing()) {
    482     Panel* resized_panel = resize_controller_->EndResizing(cancelled);
    483     if (!cancelled && resized_panel->collection())
    484       resized_panel->collection()->RefreshLayout();
    485   }
    486 }
    487 
    488 void PanelManager::OnPanelExpansionStateChanged(Panel* panel) {
    489   panel->collection()->OnPanelExpansionStateChanged(panel);
    490 }
    491 
    492 void PanelManager::MovePanelToCollection(
    493     Panel* panel,
    494     PanelCollection* target_collection,
    495     PanelCollection::PositioningMask positioning_mask) {
    496   DCHECK(panel);
    497   PanelCollection* current_collection = panel->collection();
    498   DCHECK(current_collection);
    499   DCHECK_NE(current_collection, target_collection);
    500   current_collection->RemovePanel(panel,
    501                                   PanelCollection::PANEL_CHANGED_COLLECTION);
    502 
    503   target_collection->AddPanel(panel, positioning_mask);
    504   target_collection->UpdatePanelOnCollectionChange(panel);
    505   panel->SetAlwaysOnTop(target_collection->UsesAlwaysOnTopPanels());
    506 }
    507 
    508 bool PanelManager::ShouldBringUpTitlebars(int mouse_x, int mouse_y) const {
    509   return docked_collection_->ShouldBringUpTitlebars(mouse_x, mouse_y);
    510 }
    511 
    512 void PanelManager::BringUpOrDownTitlebars(bool bring_up) {
    513   docked_collection_->BringUpOrDownTitlebars(bring_up);
    514 }
    515 
    516 void PanelManager::CloseAll() {
    517   DCHECK(!drag_controller_->is_dragging());
    518 
    519   detached_collection_->CloseAll();
    520   docked_collection_->CloseAll();
    521 }
    522 
    523 int PanelManager::num_panels() const {
    524   int count = detached_collection_->num_panels() +
    525               docked_collection_->num_panels();
    526   for (Stacks::const_iterator iter = stacks_.begin();
    527        iter != stacks_.end(); iter++)
    528     count += (*iter)->num_panels();
    529   return count;
    530 }
    531 
    532 std::vector<Panel*> PanelManager::panels() const {
    533   std::vector<Panel*> panels;
    534   for (DetachedPanelCollection::Panels::const_iterator iter =
    535            detached_collection_->panels().begin();
    536        iter != detached_collection_->panels().end(); ++iter)
    537     panels.push_back(*iter);
    538   for (DockedPanelCollection::Panels::const_iterator iter =
    539            docked_collection_->panels().begin();
    540        iter != docked_collection_->panels().end(); ++iter)
    541     panels.push_back(*iter);
    542   for (Stacks::const_iterator stack_iter = stacks_.begin();
    543        stack_iter != stacks_.end(); stack_iter++) {
    544     for (StackedPanelCollection::Panels::const_iterator iter =
    545              (*stack_iter)->panels().begin();
    546          iter != (*stack_iter)->panels().end(); ++iter) {
    547       panels.push_back(*iter);
    548     }
    549   }
    550   return panels;
    551 }
    552 
    553 std::vector<Panel*> PanelManager::GetDetachedAndStackedPanels() const {
    554   std::vector<Panel*> panels;
    555   for (DetachedPanelCollection::Panels::const_iterator iter =
    556            detached_collection_->panels().begin();
    557        iter != detached_collection_->panels().end(); ++iter)
    558     panels.push_back(*iter);
    559   for (Stacks::const_iterator stack_iter = stacks_.begin();
    560        stack_iter != stacks_.end(); stack_iter++) {
    561     for (StackedPanelCollection::Panels::const_iterator iter =
    562              (*stack_iter)->panels().begin();
    563          iter != (*stack_iter)->panels().end(); ++iter) {
    564       panels.push_back(*iter);
    565     }
    566   }
    567   return panels;
    568 }
    569 
    570 void PanelManager::SetMouseWatcher(PanelMouseWatcher* watcher) {
    571   panel_mouse_watcher_.reset(watcher);
    572 }
    573 
    574 void PanelManager::OnPanelAnimationEnded(Panel* panel) {
    575   content::NotificationService::current()->Notify(
    576       chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
    577       content::Source<Panel>(panel),
    578       content::NotificationService::NoDetails());
    579 }
    580