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