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.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "chrome/app/chrome_command_ids.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/devtools/devtools_window.h"
     13 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
     14 #include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
     15 #include "chrome/browser/extensions/api/tabs/windows_event_router.h"
     16 #include "chrome/browser/extensions/extension_service.h"
     17 #include "chrome/browser/extensions/extension_tab_util.h"
     18 #include "chrome/browser/extensions/window_controller.h"
     19 #include "chrome/browser/extensions/window_controller_list.h"
     20 #include "chrome/browser/lifetime/application_lifetime.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/themes/theme_service.h"
     23 #include "chrome/browser/themes/theme_service_factory.h"
     24 #include "chrome/browser/ui/panels/native_panel.h"
     25 #include "chrome/browser/ui/panels/panel_collection.h"
     26 #include "chrome/browser/ui/panels/panel_host.h"
     27 #include "chrome/browser/ui/panels/panel_manager.h"
     28 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
     29 #include "chrome/browser/web_applications/web_app.h"
     30 #include "content/public/browser/notification_service.h"
     31 #include "content/public/browser/notification_source.h"
     32 #include "content/public/browser/notification_types.h"
     33 #include "content/public/browser/render_view_host.h"
     34 #include "content/public/browser/user_metrics.h"
     35 #include "content/public/browser/web_contents.h"
     36 #include "extensions/browser/extension_system.h"
     37 #include "extensions/browser/image_loader.h"
     38 #include "extensions/common/constants.h"
     39 #include "extensions/common/extension.h"
     40 #include "extensions/common/manifest_handlers/icons_handler.h"
     41 #include "ui/gfx/image/image.h"
     42 #include "ui/gfx/rect.h"
     43 
     44 using base::UserMetricsAction;
     45 using content::RenderViewHost;
     46 
     47 namespace panel_internal {
     48 
     49 class PanelExtensionWindowController : public extensions::WindowController {
     50  public:
     51   PanelExtensionWindowController(Panel* panel, Profile* profile);
     52   virtual ~PanelExtensionWindowController();
     53 
     54   // Overridden from extensions::WindowController.
     55   virtual int GetWindowId() const OVERRIDE;
     56   virtual std::string GetWindowTypeText() const OVERRIDE;
     57   virtual base::DictionaryValue* CreateWindowValueWithTabs(
     58       const extensions::Extension* extension) const OVERRIDE;
     59   virtual base::DictionaryValue* CreateTabValue(
     60       const extensions::Extension* extension, int tab_index) const OVERRIDE;
     61   virtual bool CanClose(Reason* reason) const OVERRIDE;
     62   virtual void SetFullscreenMode(bool is_fullscreen,
     63                                  const GURL& extension_url) const OVERRIDE;
     64   virtual bool IsVisibleToExtension(
     65       const extensions::Extension* extension) const OVERRIDE;
     66 
     67  private:
     68   Panel* panel_;  // Weak pointer. Owns us.
     69   DISALLOW_COPY_AND_ASSIGN(PanelExtensionWindowController);
     70 };
     71 
     72 PanelExtensionWindowController::PanelExtensionWindowController(
     73     Panel* panel, Profile* profile)
     74     : extensions::WindowController(panel, profile),
     75       panel_(panel) {
     76   extensions::WindowControllerList::GetInstance()->AddExtensionWindow(this);
     77 }
     78 
     79 PanelExtensionWindowController::~PanelExtensionWindowController() {
     80   extensions::WindowControllerList::GetInstance()->RemoveExtensionWindow(this);
     81 }
     82 
     83 int PanelExtensionWindowController::GetWindowId() const {
     84   return static_cast<int>(panel_->session_id().id());
     85 }
     86 
     87 std::string PanelExtensionWindowController::GetWindowTypeText() const {
     88   return extensions::tabs_constants::kWindowTypeValuePanel;
     89 }
     90 
     91 base::DictionaryValue*
     92 PanelExtensionWindowController::CreateWindowValueWithTabs(
     93     const extensions::Extension* extension) const {
     94   base::DictionaryValue* result = CreateWindowValue();
     95 
     96   DCHECK(IsVisibleToExtension(extension));
     97   base::DictionaryValue* tab_value = CreateTabValue(extension, 0);
     98   if (tab_value) {
     99     base::ListValue* tab_list = new base::ListValue();
    100     tab_list->Append(tab_value);
    101     result->Set(extensions::tabs_constants::kTabsKey, tab_list);
    102   }
    103   return result;
    104 }
    105 
    106 base::DictionaryValue* PanelExtensionWindowController::CreateTabValue(
    107     const extensions::Extension* extension, int tab_index) const {
    108   if (tab_index > 0)
    109     return NULL;
    110 
    111   content::WebContents* web_contents = panel_->GetWebContents();
    112   if (!web_contents)
    113     return NULL;
    114 
    115   DCHECK(IsVisibleToExtension(extension));
    116   base::DictionaryValue* tab_value = new base::DictionaryValue();
    117   tab_value->SetInteger(extensions::tabs_constants::kIdKey,
    118                         SessionID::IdForTab(web_contents));
    119   tab_value->SetInteger(extensions::tabs_constants::kIndexKey, 0);
    120   tab_value->SetInteger(extensions::tabs_constants::kWindowIdKey,
    121                         SessionID::IdForWindowContainingTab(web_contents));
    122   tab_value->SetString(
    123       extensions::tabs_constants::kUrlKey, web_contents->GetURL().spec());
    124   tab_value->SetString(extensions::tabs_constants::kStatusKey,
    125                        extensions::ExtensionTabUtil::GetTabStatusText(
    126                            web_contents->IsLoading()));
    127   tab_value->SetBoolean(
    128       extensions::tabs_constants::kActiveKey, panel_->IsActive());
    129   tab_value->SetBoolean(extensions::tabs_constants::kSelectedKey, true);
    130   tab_value->SetBoolean(extensions::tabs_constants::kHighlightedKey, true);
    131   tab_value->SetBoolean(extensions::tabs_constants::kPinnedKey, false);
    132   tab_value->SetString(
    133       extensions::tabs_constants::kTitleKey, web_contents->GetTitle());
    134   tab_value->SetBoolean(
    135       extensions::tabs_constants::kIncognitoKey,
    136       web_contents->GetBrowserContext()->IsOffTheRecord());
    137   return tab_value;
    138 }
    139 
    140 bool PanelExtensionWindowController::CanClose(Reason* reason) const {
    141   return true;
    142 }
    143 
    144 void PanelExtensionWindowController::SetFullscreenMode(
    145     bool is_fullscreen, const GURL& extension_url) const {
    146   // Do nothing. Panels cannot be fullscreen.
    147 }
    148 
    149 bool PanelExtensionWindowController::IsVisibleToExtension(
    150     const extensions::Extension* extension) const {
    151   return extension->id() == panel_->extension_id();
    152 }
    153 
    154 }  // namespace panel_internal
    155 
    156 Panel::~Panel() {
    157   DCHECK(!collection_);
    158 #if !defined(USE_AURA)
    159   // Invoked by native panel destructor. Do not access native_panel_ here.
    160   chrome::DecrementKeepAliveCount();  // Remove shutdown prevention.
    161 #endif
    162 }
    163 
    164 PanelManager* Panel::manager() const {
    165   return PanelManager::GetInstance();
    166 }
    167 
    168 const std::string Panel::extension_id() const {
    169   return web_app::GetExtensionIdFromApplicationName(app_name_);
    170 }
    171 
    172 CommandUpdater* Panel::command_updater() {
    173   return &command_updater_;
    174 }
    175 
    176 Profile* Panel::profile() const {
    177   return profile_;
    178 }
    179 
    180 const extensions::Extension* Panel::GetExtension() const {
    181   ExtensionService* extension_service =
    182       extensions::ExtensionSystem::Get(profile())->extension_service();
    183   if (!extension_service || !extension_service->is_ready())
    184     return NULL;
    185   return extension_service->GetExtensionById(extension_id(), false);
    186 }
    187 
    188 content::WebContents* Panel::GetWebContents() const {
    189   return panel_host_.get() ? panel_host_->web_contents() : NULL;
    190 }
    191 
    192 void Panel::SetExpansionState(ExpansionState new_state) {
    193   if (expansion_state_ == new_state)
    194     return;
    195   native_panel_->PanelExpansionStateChanging(expansion_state_, new_state);
    196   expansion_state_ = new_state;
    197 
    198   manager()->OnPanelExpansionStateChanged(this);
    199 
    200   DCHECK(initialized_ && collection_ != NULL);
    201   native_panel_->PreventActivationByOS(collection_->IsPanelMinimized(this));
    202   UpdateMinimizeRestoreButtonVisibility();
    203 
    204   content::NotificationService::current()->Notify(
    205       chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE,
    206       content::Source<Panel>(this),
    207       content::NotificationService::NoDetails());
    208 }
    209 
    210 bool Panel::IsDrawingAttention() const {
    211   return native_panel_->IsDrawingAttention();
    212 }
    213 
    214 void Panel::FullScreenModeChanged(bool is_full_screen) {
    215   native_panel_->FullScreenModeChanged(is_full_screen);
    216 }
    217 
    218 int Panel::TitleOnlyHeight() const {
    219   return native_panel_->TitleOnlyHeight();
    220 }
    221 
    222 bool Panel::CanShowMinimizeButton() const {
    223   return collection_ && collection_->CanShowMinimizeButton(this);
    224 }
    225 
    226 bool Panel::CanShowRestoreButton() const {
    227   return collection_ && collection_->CanShowRestoreButton(this);
    228 }
    229 
    230 bool Panel::IsActive() const {
    231   return native_panel_->IsPanelActive();
    232 }
    233 
    234 bool Panel::IsMaximized() const {
    235   // Size of panels is managed by PanelManager, they are never 'zoomed'.
    236   return false;
    237 }
    238 
    239 bool Panel::IsMinimized() const {
    240   return !collection_ || collection_->IsPanelMinimized(this);
    241 }
    242 
    243 bool Panel::IsFullscreen() const {
    244   return false;
    245 }
    246 
    247 gfx::NativeWindow Panel::GetNativeWindow() {
    248   return native_panel_->GetNativePanelWindow();
    249 }
    250 
    251 gfx::Rect Panel::GetRestoredBounds() const {
    252   gfx::Rect bounds = native_panel_->GetPanelBounds();
    253   bounds.set_y(bounds.bottom() - full_size_.height());
    254   bounds.set_x(bounds.right() - full_size_.width());
    255   bounds.set_size(full_size_);
    256   return bounds;
    257 }
    258 
    259 ui::WindowShowState Panel::GetRestoredState() const {
    260   return ui::SHOW_STATE_NORMAL;
    261 }
    262 
    263 gfx::Rect Panel::GetBounds() const {
    264   return native_panel_->GetPanelBounds();
    265 }
    266 
    267 void Panel::Show() {
    268   if (manager()->display_settings_provider()->is_full_screen() || !collection_)
    269     return;
    270 
    271   native_panel_->ShowPanel();
    272 }
    273 
    274 void Panel::Hide() {
    275   // Not implemented.
    276 }
    277 
    278 void Panel::ShowInactive() {
    279   if (manager()->display_settings_provider()->is_full_screen() || !collection_)
    280     return;
    281 
    282   native_panel_->ShowPanelInactive();
    283 }
    284 
    285 // Close() may be called multiple times if the panel window is not ready to
    286 // close on the first attempt.
    287 void Panel::Close() {
    288   native_panel_->ClosePanel();
    289 }
    290 
    291 void Panel::Activate() {
    292   if (!collection_)
    293     return;
    294 
    295   collection_->ActivatePanel(this);
    296   native_panel_->ActivatePanel();
    297 }
    298 
    299 void Panel::Deactivate() {
    300   native_panel_->DeactivatePanel();
    301 }
    302 
    303 void Panel::Maximize() {
    304   Restore();
    305 }
    306 
    307 void Panel::Minimize() {
    308   if (collection_)
    309     collection_->MinimizePanel(this);
    310 }
    311 
    312 bool Panel::IsMinimizedBySystem() const {
    313   return native_panel_->IsPanelMinimizedBySystem();
    314 }
    315 
    316 bool Panel::IsShownOnActiveDesktop() const {
    317   return native_panel_->IsPanelShownOnActiveDesktop();
    318 }
    319 
    320 void Panel::ShowShadow(bool show) {
    321   native_panel_->ShowShadow(show);
    322 }
    323 
    324 void Panel::Restore() {
    325   if (collection_)
    326     collection_->RestorePanel(this);
    327 }
    328 
    329 void Panel::SetBounds(const gfx::Rect& bounds) {
    330   // Ignore bounds position as the panel manager controls all positioning.
    331   if (!collection_)
    332     return;
    333   collection_->ResizePanelWindow(this, bounds.size());
    334   SetAutoResizable(false);
    335 }
    336 
    337 void Panel::FlashFrame(bool draw_attention) {
    338   if (IsDrawingAttention() == draw_attention || !collection_)
    339     return;
    340 
    341   // Don't draw attention for an active panel.
    342   if (draw_attention && IsActive())
    343     return;
    344 
    345   // Invoking native panel to draw attention must be done before informing the
    346   // panel collection because it needs to check internal state of the panel to
    347   // determine if the panel has been drawing attention.
    348   native_panel_->DrawAttention(draw_attention);
    349   collection_->OnPanelAttentionStateChanged(this);
    350 }
    351 
    352 bool Panel::IsAlwaysOnTop() const {
    353   return native_panel_->IsPanelAlwaysOnTop();
    354 }
    355 
    356 void Panel::SetAlwaysOnTop(bool on_top) {
    357   native_panel_->SetPanelAlwaysOnTop(on_top);
    358 }
    359 
    360 void Panel::ExecuteCommandWithDisposition(int id,
    361                                           WindowOpenDisposition disposition) {
    362   DCHECK(command_updater_.IsCommandEnabled(id)) << "Invalid/disabled command "
    363                                                 << id;
    364 
    365   if (!GetWebContents())
    366     return;
    367 
    368   switch (id) {
    369     // Navigation
    370     case IDC_RELOAD:
    371       panel_host_->Reload();
    372       break;
    373     case IDC_RELOAD_IGNORING_CACHE:
    374       panel_host_->ReloadIgnoringCache();
    375       break;
    376     case IDC_STOP:
    377       panel_host_->StopLoading();
    378       break;
    379 
    380     // Window management
    381     case IDC_CLOSE_WINDOW:
    382       content::RecordAction(UserMetricsAction("CloseWindow"));
    383       Close();
    384       break;
    385     case IDC_EXIT:
    386       content::RecordAction(UserMetricsAction("Exit"));
    387       chrome::AttemptUserExit();
    388       break;
    389 
    390     // Clipboard
    391     case IDC_COPY:
    392       content::RecordAction(UserMetricsAction("Copy"));
    393       native_panel_->PanelCopy();
    394       break;
    395     case IDC_CUT:
    396       content::RecordAction(UserMetricsAction("Cut"));
    397       native_panel_->PanelCut();
    398       break;
    399     case IDC_PASTE:
    400       content::RecordAction(UserMetricsAction("Paste"));
    401       native_panel_->PanelPaste();
    402       break;
    403 
    404     // Zoom
    405     case IDC_ZOOM_PLUS:
    406       panel_host_->Zoom(content::PAGE_ZOOM_IN);
    407       break;
    408     case IDC_ZOOM_NORMAL:
    409       panel_host_->Zoom(content::PAGE_ZOOM_RESET);
    410       break;
    411     case IDC_ZOOM_MINUS:
    412       panel_host_->Zoom(content::PAGE_ZOOM_OUT);
    413       break;
    414 
    415     // DevTools
    416     case IDC_DEV_TOOLS:
    417       content::RecordAction(UserMetricsAction("DevTools_ToggleWindow"));
    418       DevToolsWindow::OpenDevToolsWindow(
    419           GetWebContents()->GetRenderViewHost(),
    420           DevToolsToggleAction::Show());
    421       break;
    422     case IDC_DEV_TOOLS_CONSOLE:
    423       content::RecordAction(UserMetricsAction("DevTools_ToggleConsole"));
    424       DevToolsWindow::OpenDevToolsWindow(
    425           GetWebContents()->GetRenderViewHost(),
    426           DevToolsToggleAction::ShowConsole());
    427       break;
    428 
    429     default:
    430       LOG(WARNING) << "Received unimplemented command: " << id;
    431       break;
    432   }
    433 }
    434 
    435 void Panel::Observe(int type,
    436                     const content::NotificationSource& source,
    437                     const content::NotificationDetails& details) {
    438   switch (type) {
    439     case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED:
    440       ConfigureAutoResize(content::Source<content::WebContents>(source).ptr());
    441       break;
    442     case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
    443       if (content::Details<extensions::UnloadedExtensionInfo>(
    444               details)->extension->id() == extension_id())
    445         Close();
    446       break;
    447     case chrome::NOTIFICATION_APP_TERMINATING:
    448       Close();
    449       break;
    450     default:
    451       NOTREACHED() << "Received unexpected notification " << type;
    452   }
    453 }
    454 
    455 void Panel::OnTitlebarClicked(panel::ClickModifier modifier) {
    456   if (collection_)
    457     collection_->OnPanelTitlebarClicked(this, modifier);
    458 
    459   // Normally the system activates a window when the titlebar is clicked.
    460   // However, we prevent system activation of minimized panels, thus the
    461   // activation may not have occurred. Also, some OSes (Windows) will
    462   // activate a minimized panel on mouse-down regardless of our attempts to
    463   // prevent system activation. Attention state is not cleared in that case.
    464   // See Panel::OnActiveStateChanged().
    465   // Therefore, we ensure activation and clearing of attention state if the
    466   // panel has been expanded. If the panel is in a stack, the titlebar click
    467   // might minimize the panel and we do not want to activate it to make it
    468   // expand again.
    469   // These are no-ops if no changes are needed.
    470   if (IsMinimized())
    471     return;
    472   Activate();
    473   FlashFrame(false);
    474 }
    475 
    476 void Panel::OnMinimizeButtonClicked(panel::ClickModifier modifier) {
    477   if (collection_)
    478     collection_->OnMinimizeButtonClicked(this, modifier);
    479 }
    480 
    481 void Panel::OnRestoreButtonClicked(panel::ClickModifier modifier) {
    482   // Clicking the restore button has the same behavior as clicking the titlebar.
    483   OnTitlebarClicked(modifier);
    484 }
    485 
    486 void Panel::OnWindowSizeAvailable() {
    487   ConfigureAutoResize(GetWebContents());
    488 }
    489 
    490 void Panel::OnNativePanelClosed() {
    491   // Ensure previously enqueued OnImageLoaded callbacks are ignored.
    492   image_loader_ptr_factory_.InvalidateWeakPtrs();
    493   registrar_.RemoveAll();
    494   manager()->OnPanelClosed(this);
    495   DCHECK(!collection_);
    496 }
    497 
    498 StackedPanelCollection* Panel::stack() const {
    499   return collection_ && collection_->type() == PanelCollection::STACKED ?
    500       static_cast<StackedPanelCollection*>(collection_) : NULL;
    501 }
    502 
    503 panel::Resizability Panel::CanResizeByMouse() const {
    504   if (!collection_)
    505     return panel::NOT_RESIZABLE;
    506 
    507   return collection_->GetPanelResizability(this);
    508 }
    509 
    510 void Panel::Initialize(const GURL& url,
    511                        const gfx::Rect& bounds,
    512                        bool always_on_top) {
    513   DCHECK(!initialized_);
    514   DCHECK(!collection_);  // Cannot be added to a collection until fully created.
    515   DCHECK_EQ(EXPANDED, expansion_state_);
    516   DCHECK(!bounds.IsEmpty());
    517   initialized_ = true;
    518   full_size_ = bounds.size();
    519   native_panel_ = CreateNativePanel(this, bounds, always_on_top);
    520 
    521   extension_window_controller_.reset(
    522       new panel_internal::PanelExtensionWindowController(this, profile_));
    523 
    524   InitCommandState();
    525 
    526   // Set up hosting for web contents.
    527   panel_host_.reset(new PanelHost(this, profile_));
    528   panel_host_->Init(url);
    529   content::WebContents* web_contents = GetWebContents();
    530   // The contents might be NULL for most of our tests.
    531   if (web_contents)
    532     native_panel_->AttachWebContents(web_contents);
    533 
    534   // Close when the extension is unloaded or the browser is exiting.
    535   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
    536                  content::Source<Profile>(profile_));
    537   registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
    538                  content::NotificationService::AllSources());
    539   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    540                  content::Source<ThemeService>(
    541                     ThemeServiceFactory::GetForProfile(profile_)));
    542 
    543 #if !defined(USE_AURA)
    544   // Keep alive for AURA has been moved to panel_view.
    545   // Prevent the browser process from shutting down while this window is open.
    546   chrome::IncrementKeepAliveCount();
    547 #endif
    548 
    549   UpdateAppIcon();
    550 }
    551 
    552 void Panel::SetPanelBounds(const gfx::Rect& bounds) {
    553   if (bounds != native_panel_->GetPanelBounds())
    554     native_panel_->SetPanelBounds(bounds);
    555 }
    556 
    557 void Panel::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
    558   native_panel_->SetPanelBoundsInstantly(bounds);
    559 }
    560 
    561 void Panel::LimitSizeToWorkArea(const gfx::Rect& work_area) {
    562   int max_width = manager()->GetMaxPanelWidth(work_area);
    563   int max_height = manager()->GetMaxPanelHeight(work_area);
    564 
    565   // If the custom max size is used, ensure that it does not exceed the display
    566   // area.
    567   if (max_size_policy_ == CUSTOM_MAX_SIZE) {
    568     int current_max_width = max_size_.width();
    569     if (current_max_width > max_width)
    570       max_width = std::min(current_max_width, work_area.width());
    571     int current_max_height = max_size_.height();
    572     if (current_max_height > max_height)
    573       max_height = std::min(current_max_height, work_area.height());
    574   }
    575 
    576   SetSizeRange(min_size_, gfx::Size(max_width, max_height));
    577 
    578   // Ensure that full size does not exceed max size.
    579   full_size_ = ClampSize(full_size_);
    580 }
    581 
    582 void Panel::SetAutoResizable(bool resizable) {
    583   if (auto_resizable_ == resizable)
    584     return;
    585 
    586   auto_resizable_ = resizable;
    587   content::WebContents* web_contents = GetWebContents();
    588   if (auto_resizable_) {
    589     if (web_contents)
    590       EnableWebContentsAutoResize(web_contents);
    591   } else {
    592     if (web_contents) {
    593       registrar_.Remove(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
    594                         content::Source<content::WebContents>(web_contents));
    595 
    596       // NULL might be returned if the tab has not been added.
    597       RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
    598       if (render_view_host)
    599         render_view_host->DisableAutoResize(full_size_);
    600     }
    601   }
    602 }
    603 
    604 void Panel::EnableWebContentsAutoResize(content::WebContents* web_contents) {
    605   DCHECK(web_contents);
    606   ConfigureAutoResize(web_contents);
    607 
    608   // We also need to know when the render view host changes in order
    609   // to turn on auto-resize notifications in the new render view host.
    610   if (!registrar_.IsRegistered(
    611           this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
    612           content::Source<content::WebContents>(web_contents))) {
    613     registrar_.Add(
    614         this,
    615         content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
    616         content::Source<content::WebContents>(web_contents));
    617   }
    618 }
    619 
    620 void Panel::OnContentsAutoResized(const gfx::Size& new_content_size) {
    621   DCHECK(auto_resizable_);
    622   if (!collection_)
    623     return;
    624 
    625   gfx::Size new_window_size =
    626       native_panel_->WindowSizeFromContentSize(new_content_size);
    627 
    628   // Ignore content auto resizes until window frame size is known.
    629   // This reduces extra resizes when panel is first shown.
    630   // After window frame size is known, it will trigger another content
    631   // auto resize.
    632   if (new_content_size == new_window_size)
    633     return;
    634 
    635   collection_->ResizePanelWindow(this, new_window_size);
    636 }
    637 
    638 void Panel::OnWindowResizedByMouse(const gfx::Rect& new_bounds) {
    639   if (collection_)
    640     collection_->OnPanelResizedByMouse(this, new_bounds);
    641 }
    642 
    643 void Panel::SetSizeRange(const gfx::Size& min_size, const gfx::Size& max_size) {
    644   if (min_size == min_size_ && max_size == max_size_)
    645     return;
    646 
    647   DCHECK(min_size.width() <= max_size.width());
    648   DCHECK(min_size.height() <= max_size.height());
    649   min_size_ = min_size;
    650   max_size_ = max_size;
    651 
    652   ConfigureAutoResize(GetWebContents());
    653 }
    654 
    655 void Panel::IncreaseMaxSize(const gfx::Size& desired_panel_size) {
    656   gfx::Size new_max_size = max_size_;
    657   if (new_max_size.width() < desired_panel_size.width())
    658     new_max_size.set_width(desired_panel_size.width());
    659   if (new_max_size.height() < desired_panel_size.height())
    660     new_max_size.set_height(desired_panel_size.height());
    661 
    662   SetSizeRange(min_size_, new_max_size);
    663 }
    664 
    665 void Panel::HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
    666   native_panel_->HandlePanelKeyboardEvent(event);
    667 }
    668 
    669 void Panel::SetPreviewMode(bool in_preview) {
    670   DCHECK_NE(in_preview_mode_, in_preview);
    671   in_preview_mode_ = in_preview;
    672 }
    673 
    674 void Panel::UpdateMinimizeRestoreButtonVisibility() {
    675   native_panel_->UpdatePanelMinimizeRestoreButtonVisibility();
    676 }
    677 
    678 gfx::Size Panel::ClampSize(const gfx::Size& size) const {
    679   // The panel width:
    680   // * cannot grow or shrink to go beyond [min_width, max_width]
    681   int new_width = size.width();
    682   if (new_width > max_size_.width())
    683     new_width = max_size_.width();
    684   if (new_width < min_size_.width())
    685     new_width = min_size_.width();
    686 
    687   // The panel height:
    688   // * cannot grow or shrink to go beyond [min_height, max_height]
    689   int new_height = size.height();
    690   if (new_height > max_size_.height())
    691     new_height = max_size_.height();
    692   if (new_height < min_size_.height())
    693     new_height = min_size_.height();
    694 
    695   return gfx::Size(new_width, new_height);
    696 }
    697 
    698 void Panel::OnActiveStateChanged(bool active) {
    699   // Clear attention state when an expanded panel becomes active.
    700   // On some systems (e.g. Win), mouse-down activates a panel regardless of
    701   // its expansion state. However, we don't want to clear draw attention if
    702   // contents are not visible. In that scenario, if the mouse-down results
    703   // in a mouse-click, draw attention will be cleared then.
    704   // See Panel::OnTitlebarClicked().
    705   if (active && IsDrawingAttention() && !IsMinimized())
    706     FlashFrame(false);
    707 
    708   if (collection_)
    709     collection_->OnPanelActiveStateChanged(this);
    710 
    711   // Send extension event about window changing active state.
    712   extensions::TabsWindowsAPI* tabs_windows_api =
    713       extensions::TabsWindowsAPI::Get(profile());
    714   if (tabs_windows_api) {
    715     tabs_windows_api->windows_event_router()->OnActiveWindowChanged(
    716         active ? extension_window_controller_.get() : NULL);
    717   }
    718 
    719   content::NotificationService::current()->Notify(
    720       chrome::NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
    721       content::Source<Panel>(this),
    722       content::NotificationService::NoDetails());
    723 }
    724 
    725 void Panel::OnPanelStartUserResizing() {
    726   SetAutoResizable(false);
    727   SetPreviewMode(true);
    728   max_size_policy_ = CUSTOM_MAX_SIZE;
    729 }
    730 
    731 void Panel::OnPanelEndUserResizing() {
    732   SetPreviewMode(false);
    733 }
    734 
    735 bool Panel::ShouldCloseWindow() {
    736   return true;
    737 }
    738 
    739 void Panel::OnWindowClosing() {
    740   if (GetWebContents()) {
    741     native_panel_->DetachWebContents(GetWebContents());
    742     panel_host_->DestroyWebContents();
    743   }
    744 }
    745 
    746 bool Panel::ExecuteCommandIfEnabled(int id) {
    747   if (command_updater()->SupportsCommand(id) &&
    748       command_updater()->IsCommandEnabled(id)) {
    749     ExecuteCommandWithDisposition(id, CURRENT_TAB);
    750     return true;
    751   }
    752   return false;
    753 }
    754 
    755 base::string16 Panel::GetWindowTitle() const {
    756   content::WebContents* contents = GetWebContents();
    757   base::string16 title;
    758 
    759   // |contents| can be NULL during the window's creation.
    760   if (contents) {
    761     title = contents->GetTitle();
    762     FormatTitleForDisplay(&title);
    763   }
    764 
    765   if (title.empty())
    766     title = base::UTF8ToUTF16(app_name());
    767 
    768   return title;
    769 }
    770 
    771 gfx::Image Panel::GetCurrentPageIcon() const {
    772   return panel_host_->GetPageIcon();
    773 }
    774 
    775 void Panel::UpdateTitleBar() {
    776   native_panel_->UpdatePanelTitleBar();
    777 }
    778 
    779 void Panel::LoadingStateChanged(bool is_loading) {
    780   command_updater_.UpdateCommandEnabled(IDC_STOP, is_loading);
    781   native_panel_->UpdatePanelLoadingAnimations(is_loading);
    782   UpdateTitleBar();
    783 }
    784 
    785 void Panel::WebContentsFocused(content::WebContents* contents) {
    786   native_panel_->PanelWebContentsFocused(contents);
    787 }
    788 
    789 void Panel::MoveByInstantly(const gfx::Vector2d& delta_origin) {
    790   gfx::Rect bounds = GetBounds();
    791   bounds.Offset(delta_origin);
    792   SetPanelBoundsInstantly(bounds);
    793 }
    794 
    795 void Panel::SetWindowCornerStyle(panel::CornerStyle corner_style) {
    796   native_panel_->SetWindowCornerStyle(corner_style);
    797 }
    798 
    799 void Panel::MinimizeBySystem() {
    800   native_panel_->MinimizePanelBySystem();
    801 }
    802 
    803 Panel::Panel(Profile* profile, const std::string& app_name,
    804              const gfx::Size& min_size, const gfx::Size& max_size)
    805     : app_name_(app_name),
    806       profile_(profile),
    807       collection_(NULL),
    808       initialized_(false),
    809       min_size_(min_size),
    810       max_size_(max_size),
    811       max_size_policy_(DEFAULT_MAX_SIZE),
    812       auto_resizable_(false),
    813       in_preview_mode_(false),
    814       native_panel_(NULL),
    815       attention_mode_(USE_PANEL_ATTENTION),
    816       expansion_state_(EXPANDED),
    817       command_updater_(this),
    818       image_loader_ptr_factory_(this) {
    819 }
    820 
    821 void Panel::OnImageLoaded(const gfx::Image& image) {
    822   if (!image.IsEmpty()) {
    823     app_icon_ = image;
    824     native_panel_->UpdatePanelTitleBar();
    825   }
    826 
    827   content::NotificationService::current()->Notify(
    828       chrome::NOTIFICATION_PANEL_APP_ICON_LOADED,
    829       content::Source<Panel>(this),
    830       content::NotificationService::NoDetails());
    831 }
    832 
    833 void Panel::InitCommandState() {
    834   // All supported commands whose state isn't set automagically some other way
    835   // (like Stop during a page load) must have their state initialized here,
    836   // otherwise they will be forever disabled.
    837 
    838   // Navigation commands
    839   command_updater_.UpdateCommandEnabled(IDC_RELOAD, true);
    840   command_updater_.UpdateCommandEnabled(IDC_RELOAD_IGNORING_CACHE, true);
    841 
    842   // Window management commands
    843   command_updater_.UpdateCommandEnabled(IDC_CLOSE_WINDOW, true);
    844   command_updater_.UpdateCommandEnabled(IDC_EXIT, true);
    845 
    846   // Zoom
    847   command_updater_.UpdateCommandEnabled(IDC_ZOOM_MENU, true);
    848   command_updater_.UpdateCommandEnabled(IDC_ZOOM_PLUS, true);
    849   command_updater_.UpdateCommandEnabled(IDC_ZOOM_NORMAL, true);
    850   command_updater_.UpdateCommandEnabled(IDC_ZOOM_MINUS, true);
    851 
    852   // Clipboard
    853   command_updater_.UpdateCommandEnabled(IDC_COPY, true);
    854   command_updater_.UpdateCommandEnabled(IDC_CUT, true);
    855   command_updater_.UpdateCommandEnabled(IDC_PASTE, true);
    856 
    857   // DevTools
    858   command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS, true);
    859   command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS_CONSOLE, true);
    860 }
    861 
    862 void Panel::ConfigureAutoResize(content::WebContents* web_contents) {
    863   if (!auto_resizable_ || !web_contents)
    864     return;
    865 
    866   // NULL might be returned if the tab has not been added.
    867   RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
    868   if (!render_view_host)
    869     return;
    870 
    871   render_view_host->EnableAutoResize(
    872       min_size_,
    873       native_panel_->ContentSizeFromWindowSize(max_size_));
    874 }
    875 
    876 void Panel::UpdateAppIcon() {
    877   const extensions::Extension* extension = GetExtension();
    878   if (!extension)
    879     return;
    880 
    881   extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile());
    882   loader->LoadImageAsync(
    883       extension,
    884       extensions::IconsInfo::GetIconResource(
    885           extension,
    886           extension_misc::EXTENSION_ICON_SMALL,
    887           ExtensionIconSet::MATCH_BIGGER),
    888       gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
    889                 extension_misc::EXTENSION_ICON_SMALL),
    890       base::Bind(&Panel::OnImageLoaded,
    891                  image_loader_ptr_factory_.GetWeakPtr()));
    892 }
    893 
    894 // static
    895 void Panel::FormatTitleForDisplay(base::string16* title) {
    896   size_t current_index = 0;
    897   size_t match_index;
    898   while ((match_index = title->find(L'\n', current_index)) !=
    899          base::string16::npos) {
    900     title->replace(match_index, 1, base::string16());
    901     current_index = match_index;
    902   }
    903 }
    904