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