普通文本  |  900行  |  28.11 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/panels/panel.h"

#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
#include "chrome/browser/extensions/api/tabs/windows_event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/image_loader.h"
#include "chrome/browser/extensions/window_controller.h"
#include "chrome/browser/extensions/window_controller_list.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/panels/native_panel.h"
#include "chrome/browser/ui/panels/panel_collection.h"
#include "chrome/browser/ui/panels/panel_host.h"
#include "chrome/browser/ui/panels/panel_manager.h"
#include "chrome/browser/ui/panels/stacked_panel_collection.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/rect.h"

using content::RenderViewHost;
using content::UserMetricsAction;

namespace panel_internal {

class PanelExtensionWindowController : public extensions::WindowController {
 public:
  PanelExtensionWindowController(Panel* panel, Profile* profile);
  virtual ~PanelExtensionWindowController();

  // Overridden from extensions::WindowController.
  virtual int GetWindowId() const OVERRIDE;
  virtual std::string GetWindowTypeText() const OVERRIDE;
  virtual base::DictionaryValue* CreateWindowValueWithTabs(
      const extensions::Extension* extension) const OVERRIDE;
  virtual base::DictionaryValue* CreateTabValue(
      const extensions::Extension* extension, int tab_index) const OVERRIDE;
  virtual bool CanClose(Reason* reason) const OVERRIDE;
  virtual void SetFullscreenMode(bool is_fullscreen,
                                 const GURL& extension_url) const OVERRIDE;
  virtual bool IsVisibleToExtension(
      const extensions::Extension* extension) const OVERRIDE;

 private:
  Panel* panel_;  // Weak pointer. Owns us.
  DISALLOW_COPY_AND_ASSIGN(PanelExtensionWindowController);
};

PanelExtensionWindowController::PanelExtensionWindowController(
    Panel* panel, Profile* profile)
    : extensions::WindowController(panel, profile),
      panel_(panel) {
  extensions::WindowControllerList::GetInstance()->AddExtensionWindow(this);
}

PanelExtensionWindowController::~PanelExtensionWindowController() {
  extensions::WindowControllerList::GetInstance()->RemoveExtensionWindow(this);
}

int PanelExtensionWindowController::GetWindowId() const {
  return static_cast<int>(panel_->session_id().id());
}

std::string PanelExtensionWindowController::GetWindowTypeText() const {
  return extensions::tabs_constants::kWindowTypeValuePanel;
}

base::DictionaryValue*
PanelExtensionWindowController::CreateWindowValueWithTabs(
    const extensions::Extension* extension) const {
  base::DictionaryValue* result = CreateWindowValue();

  DCHECK(IsVisibleToExtension(extension));
  DictionaryValue* tab_value = CreateTabValue(extension, 0);
  if (tab_value) {
    base::ListValue* tab_list = new ListValue();
    tab_list->Append(tab_value);
    result->Set(extensions::tabs_constants::kTabsKey, tab_list);
  }
  return result;
}

base::DictionaryValue* PanelExtensionWindowController::CreateTabValue(
    const extensions::Extension* extension, int tab_index) const {
  if (tab_index > 0)
    return NULL;

  content::WebContents* web_contents = panel_->GetWebContents();
  if (!web_contents)
    return NULL;

  DCHECK(IsVisibleToExtension(extension));
  DictionaryValue* tab_value = new DictionaryValue();
  tab_value->SetInteger(extensions::tabs_constants::kIdKey,
                        SessionID::IdForTab(web_contents));
  tab_value->SetInteger(extensions::tabs_constants::kIndexKey, 0);
  tab_value->SetInteger(extensions::tabs_constants::kWindowIdKey,
                        SessionID::IdForWindowContainingTab(web_contents));
  tab_value->SetString(
      extensions::tabs_constants::kUrlKey, web_contents->GetURL().spec());
  tab_value->SetString(extensions::tabs_constants::kStatusKey,
                       extensions::ExtensionTabUtil::GetTabStatusText(
                           web_contents->IsLoading()));
  tab_value->SetBoolean(
      extensions::tabs_constants::kActiveKey, panel_->IsActive());
  tab_value->SetBoolean(extensions::tabs_constants::kSelectedKey, true);
  tab_value->SetBoolean(extensions::tabs_constants::kHighlightedKey, true);
  tab_value->SetBoolean(extensions::tabs_constants::kPinnedKey, false);
  tab_value->SetString(
      extensions::tabs_constants::kTitleKey, web_contents->GetTitle());
  tab_value->SetBoolean(
      extensions::tabs_constants::kIncognitoKey,
      web_contents->GetBrowserContext()->IsOffTheRecord());
  return tab_value;
}

bool PanelExtensionWindowController::CanClose(Reason* reason) const {
  return true;
}

void PanelExtensionWindowController::SetFullscreenMode(
    bool is_fullscreen, const GURL& extension_url) const {
  // Do nothing. Panels cannot be fullscreen.
}

bool PanelExtensionWindowController::IsVisibleToExtension(
    const extensions::Extension* extension) const {
  return extension->id() == panel_->extension_id();
}

}  // namespace panel_internal

Panel::~Panel() {
  DCHECK(!collection_);
  // Invoked by native panel destructor. Do not access native_panel_ here.
  chrome::EndKeepAlive();  // Remove shutdown prevention.
}

PanelManager* Panel::manager() const {
  return PanelManager::GetInstance();
}

const std::string Panel::extension_id() const {
  return web_app::GetExtensionIdFromApplicationName(app_name_);
}

CommandUpdater* Panel::command_updater() {
  return &command_updater_;
}

Profile* Panel::profile() const {
  return profile_;
}

const extensions::Extension* Panel::GetExtension() const {
  ExtensionService* extension_service =
      extensions::ExtensionSystem::Get(profile())->extension_service();
  if (!extension_service || !extension_service->is_ready())
    return NULL;
  return extension_service->GetExtensionById(extension_id(), false);
}

content::WebContents* Panel::GetWebContents() const {
  return panel_host_.get() ? panel_host_->web_contents() : NULL;
}

void Panel::SetExpansionState(ExpansionState new_state) {
  if (expansion_state_ == new_state)
    return;
  native_panel_->PanelExpansionStateChanging(expansion_state_, new_state);
  expansion_state_ = new_state;

  manager()->OnPanelExpansionStateChanged(this);

  DCHECK(initialized_ && collection_ != NULL);
  native_panel_->PreventActivationByOS(collection_->IsPanelMinimized(this));
  UpdateMinimizeRestoreButtonVisibility();

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE,
      content::Source<Panel>(this),
      content::NotificationService::NoDetails());
}

bool Panel::IsDrawingAttention() const {
  return native_panel_->IsDrawingAttention();
}

void Panel::FullScreenModeChanged(bool is_full_screen) {
  native_panel_->FullScreenModeChanged(is_full_screen);
}

int Panel::TitleOnlyHeight() const {
  return native_panel_->TitleOnlyHeight();
}

bool Panel::CanShowMinimizeButton() const {
  return collection_ && collection_->CanShowMinimizeButton(this);
}

bool Panel::CanShowRestoreButton() const {
  return collection_ && collection_->CanShowRestoreButton(this);
}

bool Panel::IsActive() const {
  return native_panel_->IsPanelActive();
}

bool Panel::IsMaximized() const {
  // Size of panels is managed by PanelManager, they are never 'zoomed'.
  return false;
}

bool Panel::IsMinimized() const {
  return !collection_ || collection_->IsPanelMinimized(this);
}

bool Panel::IsFullscreen() const {
  return false;
}

gfx::NativeWindow Panel::GetNativeWindow() {
  return native_panel_->GetNativePanelWindow();
}

gfx::Rect Panel::GetRestoredBounds() const {
  gfx::Rect bounds = native_panel_->GetPanelBounds();
  bounds.set_y(bounds.bottom() - full_size_.height());
  bounds.set_x(bounds.right() - full_size_.width());
  bounds.set_size(full_size_);
  return bounds;
}

ui::WindowShowState Panel::GetRestoredState() const {
  return ui::SHOW_STATE_NORMAL;
}

gfx::Rect Panel::GetBounds() const {
  return native_panel_->GetPanelBounds();
}

void Panel::Show() {
  if (manager()->display_settings_provider()->is_full_screen() || !collection_)
    return;

  native_panel_->ShowPanel();
}

void Panel::Hide() {
  // Not implemented.
}

void Panel::ShowInactive() {
  if (manager()->display_settings_provider()->is_full_screen() || !collection_)
    return;

  native_panel_->ShowPanelInactive();
}

// Close() may be called multiple times if the panel window is not ready to
// close on the first attempt.
void Panel::Close() {
  native_panel_->ClosePanel();
}

void Panel::Activate() {
  if (!collection_)
    return;

  collection_->ActivatePanel(this);
  native_panel_->ActivatePanel();
}

void Panel::Deactivate() {
  native_panel_->DeactivatePanel();
}

void Panel::Maximize() {
  Restore();
}

void Panel::Minimize() {
  if (collection_)
    collection_->MinimizePanel(this);
}

bool Panel::IsMinimizedBySystem() const {
  return native_panel_->IsPanelMinimizedBySystem();
}

bool Panel::IsShownOnActiveDesktop() const {
  return native_panel_->IsPanelShownOnActiveDesktop();
}

void Panel::ShowShadow(bool show) {
  native_panel_->ShowShadow(show);
}

void Panel::Restore() {
  if (collection_)
    collection_->RestorePanel(this);
}

void Panel::SetBounds(const gfx::Rect& bounds) {
  // Ignore bounds position as the panel manager controls all positioning.
  if (!collection_)
    return;
  collection_->ResizePanelWindow(this, bounds.size());
  SetAutoResizable(false);
}

void Panel::FlashFrame(bool draw_attention) {
  if (IsDrawingAttention() == draw_attention || !collection_)
    return;

  // Don't draw attention for an active panel.
  if (draw_attention && IsActive())
    return;

  // Invoking native panel to draw attention must be done before informing the
  // panel collection because it needs to check internal state of the panel to
  // determine if the panel has been drawing attention.
  native_panel_->DrawAttention(draw_attention);
  collection_->OnPanelAttentionStateChanged(this);
}

bool Panel::IsAlwaysOnTop() const {
  return native_panel_->IsPanelAlwaysOnTop();
}

void Panel::SetAlwaysOnTop(bool on_top) {
  native_panel_->SetPanelAlwaysOnTop(on_top);
}

void Panel::ExecuteCommandWithDisposition(int id,
                                          WindowOpenDisposition disposition) {
  DCHECK(command_updater_.IsCommandEnabled(id)) << "Invalid/disabled command "
                                                << id;

  if (!GetWebContents())
    return;

  switch (id) {
    // Navigation
    case IDC_RELOAD:
      panel_host_->Reload();
      break;
    case IDC_RELOAD_IGNORING_CACHE:
      panel_host_->ReloadIgnoringCache();
      break;
    case IDC_STOP:
      panel_host_->StopLoading();
      break;

    // Window management
    case IDC_CLOSE_WINDOW:
      content::RecordAction(UserMetricsAction("CloseWindow"));
      Close();
      break;
    case IDC_EXIT:
      content::RecordAction(UserMetricsAction("Exit"));
      chrome::AttemptUserExit();
      break;

    // Clipboard
    case IDC_COPY:
      content::RecordAction(UserMetricsAction("Copy"));
      native_panel_->PanelCopy();
      break;
    case IDC_CUT:
      content::RecordAction(UserMetricsAction("Cut"));
      native_panel_->PanelCut();
      break;
    case IDC_PASTE:
      content::RecordAction(UserMetricsAction("Paste"));
      native_panel_->PanelPaste();
      break;

    // Zoom
    case IDC_ZOOM_PLUS:
      panel_host_->Zoom(content::PAGE_ZOOM_IN);
      break;
    case IDC_ZOOM_NORMAL:
      panel_host_->Zoom(content::PAGE_ZOOM_RESET);
      break;
    case IDC_ZOOM_MINUS:
      panel_host_->Zoom(content::PAGE_ZOOM_OUT);
      break;

    // DevTools
    case IDC_DEV_TOOLS:
      content::RecordAction(UserMetricsAction("DevTools_ToggleWindow"));
      DevToolsWindow::ToggleDevToolsWindow(
          GetWebContents()->GetRenderViewHost(),
          true,
          DevToolsToggleAction::Show());
      break;
    case IDC_DEV_TOOLS_CONSOLE:
      content::RecordAction(UserMetricsAction("DevTools_ToggleConsole"));
      DevToolsWindow::ToggleDevToolsWindow(
          GetWebContents()->GetRenderViewHost(),
          true,
          DevToolsToggleAction::ShowConsole());
      break;

    default:
      LOG(WARNING) << "Received unimplemented command: " << id;
      break;
  }
}

void Panel::Observe(int type,
                    const content::NotificationSource& source,
                    const content::NotificationDetails& details) {
  switch (type) {
    case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED:
      ConfigureAutoResize(content::Source<content::WebContents>(source).ptr());
      break;
    case chrome::NOTIFICATION_EXTENSION_UNLOADED:
      if (content::Details<extensions::UnloadedExtensionInfo>(
              details)->extension->id() == extension_id())
        Close();
      break;
    case chrome::NOTIFICATION_APP_TERMINATING:
      Close();
      break;
    default:
      NOTREACHED() << "Received unexpected notification " << type;
  }
}

void Panel::OnTitlebarClicked(panel::ClickModifier modifier) {
  if (collection_)
    collection_->OnPanelTitlebarClicked(this, modifier);

  // Normally the system activates a window when the titlebar is clicked.
  // However, we prevent system activation of minimized panels, thus the
  // activation may not have occurred. Also, some OSes (Windows) will
  // activate a minimized panel on mouse-down regardless of our attempts to
  // prevent system activation. Attention state is not cleared in that case.
  // See Panel::OnActiveStateChanged().
  // Therefore, we ensure activation and clearing of attention state if the
  // panel has been expanded. If the panel is in a stack, the titlebar click
  // might minimize the panel and we do not want to activate it to make it
  // expand again.
  // These are no-ops if no changes are needed.
  if (IsMinimized())
    return;
  Activate();
  FlashFrame(false);
}

void Panel::OnMinimizeButtonClicked(panel::ClickModifier modifier) {
  if (collection_)
    collection_->OnMinimizeButtonClicked(this, modifier);
}

void Panel::OnRestoreButtonClicked(panel::ClickModifier modifier) {
  // Clicking the restore button has the same behavior as clicking the titlebar.
  OnTitlebarClicked(modifier);
}

void Panel::OnWindowSizeAvailable() {
  ConfigureAutoResize(GetWebContents());
}

void Panel::OnNativePanelClosed() {
  // Ensure previously enqueued OnImageLoaded callbacks are ignored.
  image_loader_ptr_factory_.InvalidateWeakPtrs();
  registrar_.RemoveAll();
  manager()->OnPanelClosed(this);
  DCHECK(!collection_);
}

StackedPanelCollection* Panel::stack() const {
  return collection_ && collection_->type() == PanelCollection::STACKED ?
      static_cast<StackedPanelCollection*>(collection_) : NULL;
}

panel::Resizability Panel::CanResizeByMouse() const {
  if (!collection_)
    return panel::NOT_RESIZABLE;

  return collection_->GetPanelResizability(this);
}

void Panel::Initialize(const GURL& url,
                       const gfx::Rect& bounds,
                       bool always_on_top) {
  DCHECK(!initialized_);
  DCHECK(!collection_);  // Cannot be added to a collection until fully created.
  DCHECK_EQ(EXPANDED, expansion_state_);
  DCHECK(!bounds.IsEmpty());
  initialized_ = true;
  full_size_ = bounds.size();
  native_panel_ = CreateNativePanel(this, bounds, always_on_top);

  extension_window_controller_.reset(
      new panel_internal::PanelExtensionWindowController(this, profile_));

  InitCommandState();

  // Set up hosting for web contents.
  panel_host_.reset(new PanelHost(this, profile_));
  panel_host_->Init(url);
  content::WebContents* web_contents = GetWebContents();
  // The contents might be NULL for most of our tests.
  if (web_contents)
    native_panel_->AttachWebContents(web_contents);

  // Close when the extension is unloaded or the browser is exiting.
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
                 content::Source<Profile>(profile_));
  registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
                 content::Source<ThemeService>(
                    ThemeServiceFactory::GetForProfile(profile_)));

  // Prevent the browser process from shutting down while this window is open.
  chrome::StartKeepAlive();

  UpdateAppIcon();
}

void Panel::SetPanelBounds(const gfx::Rect& bounds) {
  if (bounds != native_panel_->GetPanelBounds())
    native_panel_->SetPanelBounds(bounds);
}

void Panel::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
  native_panel_->SetPanelBoundsInstantly(bounds);
}

void Panel::LimitSizeToWorkArea(const gfx::Rect& work_area) {
  int max_width = manager()->GetMaxPanelWidth(work_area);
  int max_height = manager()->GetMaxPanelHeight(work_area);

  // If the custom max size is used, ensure that it does not exceed the display
  // area.
  if (max_size_policy_ == CUSTOM_MAX_SIZE) {
    int current_max_width = max_size_.width();
    if (current_max_width > max_width)
      max_width = std::min(current_max_width, work_area.width());
    int current_max_height = max_size_.height();
    if (current_max_height > max_height)
      max_height = std::min(current_max_height, work_area.height());
  }

  SetSizeRange(min_size_, gfx::Size(max_width, max_height));

  // Ensure that full size does not exceed max size.
  full_size_ = ClampSize(full_size_);
}

void Panel::SetAutoResizable(bool resizable) {
  if (auto_resizable_ == resizable)
    return;

  auto_resizable_ = resizable;
  content::WebContents* web_contents = GetWebContents();
  if (auto_resizable_) {
    if (web_contents)
      EnableWebContentsAutoResize(web_contents);
  } else {
    if (web_contents) {
      registrar_.Remove(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
                        content::Source<content::WebContents>(web_contents));

      // NULL might be returned if the tab has not been added.
      RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
      if (render_view_host)
        render_view_host->DisableAutoResize(full_size_);
    }
  }
}

void Panel::EnableWebContentsAutoResize(content::WebContents* web_contents) {
  DCHECK(web_contents);
  ConfigureAutoResize(web_contents);

  // We also need to know when the render view host changes in order
  // to turn on auto-resize notifications in the new render view host.
  if (!registrar_.IsRegistered(
          this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
          content::Source<content::WebContents>(web_contents))) {
    registrar_.Add(
        this,
        content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
        content::Source<content::WebContents>(web_contents));
  }
}

void Panel::OnContentsAutoResized(const gfx::Size& new_content_size) {
  DCHECK(auto_resizable_);
  if (!collection_)
    return;

  gfx::Size new_window_size =
      native_panel_->WindowSizeFromContentSize(new_content_size);

  // Ignore content auto resizes until window frame size is known.
  // This reduces extra resizes when panel is first shown.
  // After window frame size is known, it will trigger another content
  // auto resize.
  if (new_content_size == new_window_size)
    return;

  collection_->ResizePanelWindow(this, new_window_size);
}

void Panel::OnWindowResizedByMouse(const gfx::Rect& new_bounds) {
  if (collection_)
    collection_->OnPanelResizedByMouse(this, new_bounds);
}

void Panel::SetSizeRange(const gfx::Size& min_size, const gfx::Size& max_size) {
  if (min_size == min_size_ && max_size == max_size_)
    return;

  DCHECK(min_size.width() <= max_size.width());
  DCHECK(min_size.height() <= max_size.height());
  min_size_ = min_size;
  max_size_ = max_size;

  ConfigureAutoResize(GetWebContents());
}

void Panel::IncreaseMaxSize(const gfx::Size& desired_panel_size) {
  gfx::Size new_max_size = max_size_;
  if (new_max_size.width() < desired_panel_size.width())
    new_max_size.set_width(desired_panel_size.width());
  if (new_max_size.height() < desired_panel_size.height())
    new_max_size.set_height(desired_panel_size.height());

  SetSizeRange(min_size_, new_max_size);
}

void Panel::HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
  native_panel_->HandlePanelKeyboardEvent(event);
}

void Panel::SetPreviewMode(bool in_preview) {
  DCHECK_NE(in_preview_mode_, in_preview);
  in_preview_mode_ = in_preview;
}

void Panel::UpdateMinimizeRestoreButtonVisibility() {
  native_panel_->UpdatePanelMinimizeRestoreButtonVisibility();
}

gfx::Size Panel::ClampSize(const gfx::Size& size) const {
  // The panel width:
  // * cannot grow or shrink to go beyond [min_width, max_width]
  int new_width = size.width();
  if (new_width > max_size_.width())
    new_width = max_size_.width();
  if (new_width < min_size_.width())
    new_width = min_size_.width();

  // The panel height:
  // * cannot grow or shrink to go beyond [min_height, max_height]
  int new_height = size.height();
  if (new_height > max_size_.height())
    new_height = max_size_.height();
  if (new_height < min_size_.height())
    new_height = min_size_.height();

  return gfx::Size(new_width, new_height);
}

void Panel::OnActiveStateChanged(bool active) {
  // Clear attention state when an expanded panel becomes active.
  // On some systems (e.g. Win), mouse-down activates a panel regardless of
  // its expansion state. However, we don't want to clear draw attention if
  // contents are not visible. In that scenario, if the mouse-down results
  // in a mouse-click, draw attention will be cleared then.
  // See Panel::OnTitlebarClicked().
  if (active && IsDrawingAttention() && !IsMinimized())
    FlashFrame(false);

  if (collection_)
    collection_->OnPanelActiveStateChanged(this);

  // Send extension event about window changing active state.
  extensions::TabsWindowsAPI* tabs_windows_api =
      extensions::TabsWindowsAPI::Get(profile());
  if (tabs_windows_api) {
    tabs_windows_api->windows_event_router()->OnActiveWindowChanged(
        active ? extension_window_controller_.get() : NULL);
  }

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
      content::Source<Panel>(this),
      content::NotificationService::NoDetails());
}

void Panel::OnPanelStartUserResizing() {
  SetAutoResizable(false);
  SetPreviewMode(true);
  max_size_policy_ = CUSTOM_MAX_SIZE;
}

void Panel::OnPanelEndUserResizing() {
  SetPreviewMode(false);
}

bool Panel::ShouldCloseWindow() {
  return true;
}

void Panel::OnWindowClosing() {
  if (GetWebContents()) {
    native_panel_->DetachWebContents(GetWebContents());
    panel_host_->DestroyWebContents();
  }
}

bool Panel::ExecuteCommandIfEnabled(int id) {
  if (command_updater()->SupportsCommand(id) &&
      command_updater()->IsCommandEnabled(id)) {
    ExecuteCommandWithDisposition(id, CURRENT_TAB);
    return true;
  }
  return false;
}

base::string16 Panel::GetWindowTitle() const {
  content::WebContents* contents = GetWebContents();
  base::string16 title;

  // |contents| can be NULL during the window's creation.
  if (contents) {
    title = contents->GetTitle();
    FormatTitleForDisplay(&title);
  }

  if (title.empty())
    title = UTF8ToUTF16(app_name());

  return title;
}

gfx::Image Panel::GetCurrentPageIcon() const {
  return panel_host_->GetPageIcon();
}

void Panel::UpdateTitleBar() {
  native_panel_->UpdatePanelTitleBar();
}

void Panel::LoadingStateChanged(bool is_loading) {
  command_updater_.UpdateCommandEnabled(IDC_STOP, is_loading);
  native_panel_->UpdatePanelLoadingAnimations(is_loading);
  UpdateTitleBar();
}

void Panel::WebContentsFocused(content::WebContents* contents) {
  native_panel_->PanelWebContentsFocused(contents);
}

void Panel::MoveByInstantly(const gfx::Vector2d& delta_origin) {
  gfx::Rect bounds = GetBounds();
  bounds.Offset(delta_origin);
  SetPanelBoundsInstantly(bounds);
}

void Panel::SetWindowCornerStyle(panel::CornerStyle corner_style) {
  native_panel_->SetWindowCornerStyle(corner_style);
}

void Panel::MinimizeBySystem() {
  native_panel_->MinimizePanelBySystem();
}

Panel::Panel(Profile* profile, const std::string& app_name,
             const gfx::Size& min_size, const gfx::Size& max_size)
    : app_name_(app_name),
      profile_(profile),
      collection_(NULL),
      initialized_(false),
      min_size_(min_size),
      max_size_(max_size),
      max_size_policy_(DEFAULT_MAX_SIZE),
      auto_resizable_(false),
      in_preview_mode_(false),
      native_panel_(NULL),
      attention_mode_(USE_PANEL_ATTENTION),
      expansion_state_(EXPANDED),
      command_updater_(this),
      image_loader_ptr_factory_(this) {
}

void Panel::OnImageLoaded(const gfx::Image& image) {
  if (!image.IsEmpty()) {
    app_icon_ = image;
    native_panel_->UpdatePanelTitleBar();
  }

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PANEL_APP_ICON_LOADED,
      content::Source<Panel>(this),
      content::NotificationService::NoDetails());
}

void Panel::InitCommandState() {
  // All supported commands whose state isn't set automagically some other way
  // (like Stop during a page load) must have their state initialized here,
  // otherwise they will be forever disabled.

  // Navigation commands
  command_updater_.UpdateCommandEnabled(IDC_RELOAD, true);
  command_updater_.UpdateCommandEnabled(IDC_RELOAD_IGNORING_CACHE, true);

  // Window management commands
  command_updater_.UpdateCommandEnabled(IDC_CLOSE_WINDOW, true);
  command_updater_.UpdateCommandEnabled(IDC_EXIT, true);

  // Zoom
  command_updater_.UpdateCommandEnabled(IDC_ZOOM_MENU, true);
  command_updater_.UpdateCommandEnabled(IDC_ZOOM_PLUS, true);
  command_updater_.UpdateCommandEnabled(IDC_ZOOM_NORMAL, true);
  command_updater_.UpdateCommandEnabled(IDC_ZOOM_MINUS, true);

  // Clipboard
  command_updater_.UpdateCommandEnabled(IDC_COPY, true);
  command_updater_.UpdateCommandEnabled(IDC_CUT, true);
  command_updater_.UpdateCommandEnabled(IDC_PASTE, true);

  // DevTools
  command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS, true);
  command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS_CONSOLE, true);
}

void Panel::ConfigureAutoResize(content::WebContents* web_contents) {
  if (!auto_resizable_ || !web_contents)
    return;

  // NULL might be returned if the tab has not been added.
  RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
  if (!render_view_host)
    return;

  render_view_host->EnableAutoResize(
      min_size_,
      native_panel_->ContentSizeFromWindowSize(max_size_));
}

void Panel::UpdateAppIcon() {
  const extensions::Extension* extension = GetExtension();
  if (!extension)
    return;

  extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile());
  loader->LoadImageAsync(
      extension,
      extensions::IconsInfo::GetIconResource(
          extension,
          extension_misc::EXTENSION_ICON_SMALL,
          ExtensionIconSet::MATCH_BIGGER),
      gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
                extension_misc::EXTENSION_ICON_SMALL),
      base::Bind(&Panel::OnImageLoaded,
                 image_loader_ptr_factory_.GetWeakPtr()));
}

// static
void Panel::FormatTitleForDisplay(base::string16* title) {
  size_t current_index = 0;
  size_t match_index;
  while ((match_index = title->find(L'\n', current_index)) !=
         base::string16::npos) {
    title->replace(match_index, 1, base::string16());
    current_index = match_index;
  }
}