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