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