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