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
46using base::UserMetricsAction;
47using content::RenderViewHost;
48
49namespace panel_internal {
50
51class 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
74PanelExtensionWindowController::PanelExtensionWindowController(
75    Panel* panel, Profile* profile)
76    : extensions::WindowController(panel, profile),
77      panel_(panel) {
78  extensions::WindowControllerList::GetInstance()->AddExtensionWindow(this);
79}
80
81PanelExtensionWindowController::~PanelExtensionWindowController() {
82  extensions::WindowControllerList::GetInstance()->RemoveExtensionWindow(this);
83}
84
85int PanelExtensionWindowController::GetWindowId() const {
86  return static_cast<int>(panel_->session_id().id());
87}
88
89std::string PanelExtensionWindowController::GetWindowTypeText() const {
90  return extensions::tabs_constants::kWindowTypeValuePanel;
91}
92
93base::DictionaryValue*
94PanelExtensionWindowController::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
108base::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
143bool PanelExtensionWindowController::CanClose(Reason* reason) const {
144  return true;
145}
146
147void PanelExtensionWindowController::SetFullscreenMode(
148    bool is_fullscreen, const GURL& extension_url) const {
149  // Do nothing. Panels cannot be fullscreen.
150}
151
152bool PanelExtensionWindowController::IsVisibleToExtension(
153    const extensions::Extension* extension) const {
154  return extension->id() == panel_->extension_id();
155}
156
157}  // namespace panel_internal
158
159Panel::~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
167PanelManager* Panel::manager() const {
168  return PanelManager::GetInstance();
169}
170
171const std::string Panel::extension_id() const {
172  return web_app::GetExtensionIdFromApplicationName(app_name_);
173}
174
175CommandUpdater* Panel::command_updater() {
176  return &command_updater_;
177}
178
179Profile* Panel::profile() const {
180  return profile_;
181}
182
183const 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
191content::WebContents* Panel::GetWebContents() const {
192  return panel_host_.get() ? panel_host_->web_contents() : NULL;
193}
194
195void 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
213bool Panel::IsDrawingAttention() const {
214  return native_panel_->IsDrawingAttention();
215}
216
217void Panel::FullScreenModeChanged(bool is_full_screen) {
218  native_panel_->FullScreenModeChanged(is_full_screen);
219}
220
221int Panel::TitleOnlyHeight() const {
222  return native_panel_->TitleOnlyHeight();
223}
224
225bool Panel::CanShowMinimizeButton() const {
226  return collection_ && collection_->CanShowMinimizeButton(this);
227}
228
229bool Panel::CanShowRestoreButton() const {
230  return collection_ && collection_->CanShowRestoreButton(this);
231}
232
233bool Panel::IsActive() const {
234  return native_panel_->IsPanelActive();
235}
236
237bool Panel::IsMaximized() const {
238  // Size of panels is managed by PanelManager, they are never 'zoomed'.
239  return false;
240}
241
242bool Panel::IsMinimized() const {
243  return !collection_ || collection_->IsPanelMinimized(this);
244}
245
246bool Panel::IsFullscreen() const {
247  return false;
248}
249
250gfx::NativeWindow Panel::GetNativeWindow() {
251  return native_panel_->GetNativePanelWindow();
252}
253
254gfx::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
262ui::WindowShowState Panel::GetRestoredState() const {
263  return ui::SHOW_STATE_NORMAL;
264}
265
266gfx::Rect Panel::GetBounds() const {
267  return native_panel_->GetPanelBounds();
268}
269
270void Panel::Show() {
271  if (manager()->display_settings_provider()->is_full_screen() || !collection_)
272    return;
273
274  native_panel_->ShowPanel();
275}
276
277void Panel::Hide() {
278  // Not implemented.
279}
280
281void 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.
290void Panel::Close() {
291  native_panel_->ClosePanel();
292}
293
294void Panel::Activate() {
295  if (!collection_)
296    return;
297
298  collection_->ActivatePanel(this);
299  native_panel_->ActivatePanel();
300}
301
302void Panel::Deactivate() {
303  native_panel_->DeactivatePanel();
304}
305
306void Panel::Maximize() {
307  Restore();
308}
309
310void Panel::Minimize() {
311  if (collection_)
312    collection_->MinimizePanel(this);
313}
314
315bool Panel::IsMinimizedBySystem() const {
316  return native_panel_->IsPanelMinimizedBySystem();
317}
318
319bool Panel::IsShownOnActiveDesktop() const {
320  return native_panel_->IsPanelShownOnActiveDesktop();
321}
322
323void Panel::ShowShadow(bool show) {
324  native_panel_->ShowShadow(show);
325}
326
327void Panel::Restore() {
328  if (collection_)
329    collection_->RestorePanel(this);
330}
331
332void 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
340void 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
355bool Panel::IsAlwaysOnTop() const {
356  return native_panel_->IsPanelAlwaysOnTop();
357}
358
359void Panel::SetAlwaysOnTop(bool on_top) {
360  native_panel_->SetPanelAlwaysOnTop(on_top);
361}
362
363void 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
436void 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
451void 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}
458void 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
479void Panel::OnMinimizeButtonClicked(panel::ClickModifier modifier) {
480  if (collection_)
481    collection_->OnMinimizeButtonClicked(this, modifier);
482}
483
484void Panel::OnRestoreButtonClicked(panel::ClickModifier modifier) {
485  // Clicking the restore button has the same behavior as clicking the titlebar.
486  OnTitlebarClicked(modifier);
487}
488
489void Panel::OnWindowSizeAvailable() {
490  ConfigureAutoResize(GetWebContents());
491}
492
493void 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
502StackedPanelCollection* Panel::stack() const {
503  return collection_ && collection_->type() == PanelCollection::STACKED ?
504      static_cast<StackedPanelCollection*>(collection_) : NULL;
505}
506
507panel::Resizability Panel::CanResizeByMouse() const {
508  if (!collection_)
509    return panel::NOT_RESIZABLE;
510
511  return collection_->GetPanelResizability(this);
512}
513
514void 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
555void Panel::SetPanelBounds(const gfx::Rect& bounds) {
556  if (bounds != native_panel_->GetPanelBounds())
557    native_panel_->SetPanelBounds(bounds);
558}
559
560void Panel::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
561  native_panel_->SetPanelBoundsInstantly(bounds);
562}
563
564void 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
585void 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
607void 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
623void 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
641void Panel::OnWindowResizedByMouse(const gfx::Rect& new_bounds) {
642  if (collection_)
643    collection_->OnPanelResizedByMouse(this, new_bounds);
644}
645
646void 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
658void 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
668void Panel::HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
669  native_panel_->HandlePanelKeyboardEvent(event);
670}
671
672void Panel::SetPreviewMode(bool in_preview) {
673  DCHECK_NE(in_preview_mode_, in_preview);
674  in_preview_mode_ = in_preview;
675}
676
677void Panel::UpdateMinimizeRestoreButtonVisibility() {
678  native_panel_->UpdatePanelMinimizeRestoreButtonVisibility();
679}
680
681gfx::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
701void 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
728void Panel::OnPanelStartUserResizing() {
729  SetAutoResizable(false);
730  SetPreviewMode(true);
731  max_size_policy_ = CUSTOM_MAX_SIZE;
732}
733
734void Panel::OnPanelEndUserResizing() {
735  SetPreviewMode(false);
736}
737
738bool Panel::ShouldCloseWindow() {
739  return true;
740}
741
742void Panel::OnWindowClosing() {
743  if (GetWebContents()) {
744    native_panel_->DetachWebContents(GetWebContents());
745    panel_host_->DestroyWebContents();
746  }
747}
748
749bool 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
758base::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
774gfx::Image Panel::GetCurrentPageIcon() const {
775  return panel_host_->GetPageIcon();
776}
777
778void Panel::UpdateTitleBar() {
779  native_panel_->UpdatePanelTitleBar();
780}
781
782void Panel::LoadingStateChanged(bool is_loading) {
783  command_updater_.UpdateCommandEnabled(IDC_STOP, is_loading);
784  native_panel_->UpdatePanelLoadingAnimations(is_loading);
785  UpdateTitleBar();
786}
787
788void Panel::WebContentsFocused(content::WebContents* contents) {
789  native_panel_->PanelWebContentsFocused(contents);
790}
791
792void Panel::MoveByInstantly(const gfx::Vector2d& delta_origin) {
793  gfx::Rect bounds = GetBounds();
794  bounds.Offset(delta_origin);
795  SetPanelBoundsInstantly(bounds);
796}
797
798void Panel::SetWindowCornerStyle(panel::CornerStyle corner_style) {
799  native_panel_->SetWindowCornerStyle(corner_style);
800}
801
802void Panel::MinimizeBySystem() {
803  native_panel_->MinimizePanelBySystem();
804}
805
806Panel::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
827void 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
839void 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
868void 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
882void 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
901void 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