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_system.h"
18 #include "chrome/browser/extensions/extension_tab_util.h"
19 #include "chrome/browser/extensions/image_loader.h"
20 #include "chrome/browser/extensions/window_controller.h"
21 #include "chrome/browser/extensions/window_controller_list.h"
22 #include "chrome/browser/lifetime/application_lifetime.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/themes/theme_service.h"
25 #include "chrome/browser/themes/theme_service_factory.h"
26 #include "chrome/browser/ui/panels/native_panel.h"
27 #include "chrome/browser/ui/panels/panel_collection.h"
28 #include "chrome/browser/ui/panels/panel_host.h"
29 #include "chrome/browser/ui/panels/panel_manager.h"
30 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
31 #include "chrome/browser/web_applications/web_app.h"
32 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
33 #include "content/public/browser/notification_service.h"
34 #include "content/public/browser/notification_source.h"
35 #include "content/public/browser/notification_types.h"
36 #include "content/public/browser/render_view_host.h"
37 #include "content/public/browser/user_metrics.h"
38 #include "content/public/browser/web_contents.h"
39 #include "extensions/common/extension.h"
40 #include "ui/gfx/image/image.h"
41 #include "ui/gfx/rect.h"
42
43 using content::RenderViewHost;
44 using content::UserMetricsAction;
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
PanelExtensionWindowController(Panel * panel,Profile * profile)71 PanelExtensionWindowController::PanelExtensionWindowController(
72 Panel* panel, Profile* profile)
73 : extensions::WindowController(panel, profile),
74 panel_(panel) {
75 extensions::WindowControllerList::GetInstance()->AddExtensionWindow(this);
76 }
77
~PanelExtensionWindowController()78 PanelExtensionWindowController::~PanelExtensionWindowController() {
79 extensions::WindowControllerList::GetInstance()->RemoveExtensionWindow(this);
80 }
81
GetWindowId() const82 int PanelExtensionWindowController::GetWindowId() const {
83 return static_cast<int>(panel_->session_id().id());
84 }
85
GetWindowTypeText() const86 std::string PanelExtensionWindowController::GetWindowTypeText() const {
87 return extensions::tabs_constants::kWindowTypeValuePanel;
88 }
89
90 base::DictionaryValue*
CreateWindowValueWithTabs(const extensions::Extension * extension) const91 PanelExtensionWindowController::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
CreateTabValue(const extensions::Extension * extension,int tab_index) const105 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 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 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
CanClose(Reason * reason) const139 bool PanelExtensionWindowController::CanClose(Reason* reason) const {
140 return true;
141 }
142
SetFullscreenMode(bool is_fullscreen,const GURL & extension_url) const143 void PanelExtensionWindowController::SetFullscreenMode(
144 bool is_fullscreen, const GURL& extension_url) const {
145 // Do nothing. Panels cannot be fullscreen.
146 }
147
IsVisibleToExtension(const extensions::Extension * extension) const148 bool PanelExtensionWindowController::IsVisibleToExtension(
149 const extensions::Extension* extension) const {
150 return extension->id() == panel_->extension_id();
151 }
152
153 } // namespace panel_internal
154
~Panel()155 Panel::~Panel() {
156 DCHECK(!collection_);
157 // Invoked by native panel destructor. Do not access native_panel_ here.
158 chrome::EndKeepAlive(); // Remove shutdown prevention.
159 }
160
manager() const161 PanelManager* Panel::manager() const {
162 return PanelManager::GetInstance();
163 }
164
extension_id() const165 const std::string Panel::extension_id() const {
166 return web_app::GetExtensionIdFromApplicationName(app_name_);
167 }
168
command_updater()169 CommandUpdater* Panel::command_updater() {
170 return &command_updater_;
171 }
172
profile() const173 Profile* Panel::profile() const {
174 return profile_;
175 }
176
GetExtension() const177 const 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
GetWebContents() const185 content::WebContents* Panel::GetWebContents() const {
186 return panel_host_.get() ? panel_host_->web_contents() : NULL;
187 }
188
SetExpansionState(ExpansionState new_state)189 void 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
IsDrawingAttention() const207 bool Panel::IsDrawingAttention() const {
208 return native_panel_->IsDrawingAttention();
209 }
210
FullScreenModeChanged(bool is_full_screen)211 void Panel::FullScreenModeChanged(bool is_full_screen) {
212 native_panel_->FullScreenModeChanged(is_full_screen);
213 }
214
TitleOnlyHeight() const215 int Panel::TitleOnlyHeight() const {
216 return native_panel_->TitleOnlyHeight();
217 }
218
CanShowMinimizeButton() const219 bool Panel::CanShowMinimizeButton() const {
220 return collection_ && collection_->CanShowMinimizeButton(this);
221 }
222
CanShowRestoreButton() const223 bool Panel::CanShowRestoreButton() const {
224 return collection_ && collection_->CanShowRestoreButton(this);
225 }
226
IsActive() const227 bool Panel::IsActive() const {
228 return native_panel_->IsPanelActive();
229 }
230
IsMaximized() const231 bool Panel::IsMaximized() const {
232 // Size of panels is managed by PanelManager, they are never 'zoomed'.
233 return false;
234 }
235
IsMinimized() const236 bool Panel::IsMinimized() const {
237 return !collection_ || collection_->IsPanelMinimized(this);
238 }
239
IsFullscreen() const240 bool Panel::IsFullscreen() const {
241 return false;
242 }
243
GetNativeWindow()244 gfx::NativeWindow Panel::GetNativeWindow() {
245 return native_panel_->GetNativePanelWindow();
246 }
247
GetRestoredBounds() const248 gfx::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
GetRestoredState() const256 ui::WindowShowState Panel::GetRestoredState() const {
257 return ui::SHOW_STATE_NORMAL;
258 }
259
GetBounds() const260 gfx::Rect Panel::GetBounds() const {
261 return native_panel_->GetPanelBounds();
262 }
263
Show()264 void Panel::Show() {
265 if (manager()->display_settings_provider()->is_full_screen() || !collection_)
266 return;
267
268 native_panel_->ShowPanel();
269 }
270
Hide()271 void Panel::Hide() {
272 // Not implemented.
273 }
274
ShowInactive()275 void Panel::ShowInactive() {
276 if (manager()->display_settings_provider()->is_full_screen() || !collection_)
277 return;
278
279 native_panel_->ShowPanelInactive();
280 }
281
282 // Close() may be called multiple times if the panel window is not ready to
283 // close on the first attempt.
Close()284 void Panel::Close() {
285 native_panel_->ClosePanel();
286 }
287
Activate()288 void Panel::Activate() {
289 if (!collection_)
290 return;
291
292 collection_->ActivatePanel(this);
293 native_panel_->ActivatePanel();
294 }
295
Deactivate()296 void Panel::Deactivate() {
297 native_panel_->DeactivatePanel();
298 }
299
Maximize()300 void Panel::Maximize() {
301 Restore();
302 }
303
Minimize()304 void Panel::Minimize() {
305 if (collection_)
306 collection_->MinimizePanel(this);
307 }
308
IsMinimizedBySystem() const309 bool Panel::IsMinimizedBySystem() const {
310 return native_panel_->IsPanelMinimizedBySystem();
311 }
312
IsShownOnActiveDesktop() const313 bool Panel::IsShownOnActiveDesktop() const {
314 return native_panel_->IsPanelShownOnActiveDesktop();
315 }
316
ShowShadow(bool show)317 void Panel::ShowShadow(bool show) {
318 native_panel_->ShowShadow(show);
319 }
320
Restore()321 void Panel::Restore() {
322 if (collection_)
323 collection_->RestorePanel(this);
324 }
325
SetBounds(const gfx::Rect & bounds)326 void Panel::SetBounds(const gfx::Rect& bounds) {
327 // Ignore bounds position as the panel manager controls all positioning.
328 if (!collection_)
329 return;
330 collection_->ResizePanelWindow(this, bounds.size());
331 SetAutoResizable(false);
332 }
333
FlashFrame(bool draw_attention)334 void Panel::FlashFrame(bool draw_attention) {
335 if (IsDrawingAttention() == draw_attention || !collection_)
336 return;
337
338 // Don't draw attention for an active panel.
339 if (draw_attention && IsActive())
340 return;
341
342 // Invoking native panel to draw attention must be done before informing the
343 // panel collection because it needs to check internal state of the panel to
344 // determine if the panel has been drawing attention.
345 native_panel_->DrawAttention(draw_attention);
346 collection_->OnPanelAttentionStateChanged(this);
347 }
348
IsAlwaysOnTop() const349 bool Panel::IsAlwaysOnTop() const {
350 return native_panel_->IsPanelAlwaysOnTop();
351 }
352
SetAlwaysOnTop(bool on_top)353 void Panel::SetAlwaysOnTop(bool on_top) {
354 native_panel_->SetPanelAlwaysOnTop(on_top);
355 }
356
ExecuteCommandWithDisposition(int id,WindowOpenDisposition disposition)357 void Panel::ExecuteCommandWithDisposition(int id,
358 WindowOpenDisposition disposition) {
359 DCHECK(command_updater_.IsCommandEnabled(id)) << "Invalid/disabled command "
360 << id;
361
362 if (!GetWebContents())
363 return;
364
365 switch (id) {
366 // Navigation
367 case IDC_RELOAD:
368 panel_host_->Reload();
369 break;
370 case IDC_RELOAD_IGNORING_CACHE:
371 panel_host_->ReloadIgnoringCache();
372 break;
373 case IDC_STOP:
374 panel_host_->StopLoading();
375 break;
376
377 // Window management
378 case IDC_CLOSE_WINDOW:
379 content::RecordAction(UserMetricsAction("CloseWindow"));
380 Close();
381 break;
382 case IDC_EXIT:
383 content::RecordAction(UserMetricsAction("Exit"));
384 chrome::AttemptUserExit();
385 break;
386
387 // Clipboard
388 case IDC_COPY:
389 content::RecordAction(UserMetricsAction("Copy"));
390 native_panel_->PanelCopy();
391 break;
392 case IDC_CUT:
393 content::RecordAction(UserMetricsAction("Cut"));
394 native_panel_->PanelCut();
395 break;
396 case IDC_PASTE:
397 content::RecordAction(UserMetricsAction("Paste"));
398 native_panel_->PanelPaste();
399 break;
400
401 // Zoom
402 case IDC_ZOOM_PLUS:
403 panel_host_->Zoom(content::PAGE_ZOOM_IN);
404 break;
405 case IDC_ZOOM_NORMAL:
406 panel_host_->Zoom(content::PAGE_ZOOM_RESET);
407 break;
408 case IDC_ZOOM_MINUS:
409 panel_host_->Zoom(content::PAGE_ZOOM_OUT);
410 break;
411
412 // DevTools
413 case IDC_DEV_TOOLS:
414 content::RecordAction(UserMetricsAction("DevTools_ToggleWindow"));
415 DevToolsWindow::ToggleDevToolsWindow(
416 GetWebContents()->GetRenderViewHost(),
417 true,
418 DevToolsToggleAction::Show());
419 break;
420 case IDC_DEV_TOOLS_CONSOLE:
421 content::RecordAction(UserMetricsAction("DevTools_ToggleConsole"));
422 DevToolsWindow::ToggleDevToolsWindow(
423 GetWebContents()->GetRenderViewHost(),
424 true,
425 DevToolsToggleAction::ShowConsole());
426 break;
427
428 default:
429 LOG(WARNING) << "Received unimplemented command: " << id;
430 break;
431 }
432 }
433
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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:
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
OnTitlebarClicked(panel::ClickModifier modifier)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
OnMinimizeButtonClicked(panel::ClickModifier modifier)475 void Panel::OnMinimizeButtonClicked(panel::ClickModifier modifier) {
476 if (collection_)
477 collection_->OnMinimizeButtonClicked(this, modifier);
478 }
479
OnRestoreButtonClicked(panel::ClickModifier modifier)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
OnWindowSizeAvailable()485 void Panel::OnWindowSizeAvailable() {
486 ConfigureAutoResize(GetWebContents());
487 }
488
OnNativePanelClosed()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
stack() const497 StackedPanelCollection* Panel::stack() const {
498 return collection_ && collection_->type() == PanelCollection::STACKED ?
499 static_cast<StackedPanelCollection*>(collection_) : NULL;
500 }
501
CanResizeByMouse() const502 panel::Resizability Panel::CanResizeByMouse() const {
503 if (!collection_)
504 return panel::NOT_RESIZABLE;
505
506 return collection_->GetPanelResizability(this);
507 }
508
Initialize(const GURL & url,const gfx::Rect & bounds,bool always_on_top)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,
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 // Prevent the browser process from shutting down while this window is open.
543 chrome::StartKeepAlive();
544
545 UpdateAppIcon();
546 }
547
SetPanelBounds(const gfx::Rect & bounds)548 void Panel::SetPanelBounds(const gfx::Rect& bounds) {
549 if (bounds != native_panel_->GetPanelBounds())
550 native_panel_->SetPanelBounds(bounds);
551 }
552
SetPanelBoundsInstantly(const gfx::Rect & bounds)553 void Panel::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
554 native_panel_->SetPanelBoundsInstantly(bounds);
555 }
556
LimitSizeToWorkArea(const gfx::Rect & work_area)557 void Panel::LimitSizeToWorkArea(const gfx::Rect& work_area) {
558 int max_width = manager()->GetMaxPanelWidth(work_area);
559 int max_height = manager()->GetMaxPanelHeight(work_area);
560
561 // If the custom max size is used, ensure that it does not exceed the display
562 // area.
563 if (max_size_policy_ == CUSTOM_MAX_SIZE) {
564 int current_max_width = max_size_.width();
565 if (current_max_width > max_width)
566 max_width = std::min(current_max_width, work_area.width());
567 int current_max_height = max_size_.height();
568 if (current_max_height > max_height)
569 max_height = std::min(current_max_height, work_area.height());
570 }
571
572 SetSizeRange(min_size_, gfx::Size(max_width, max_height));
573
574 // Ensure that full size does not exceed max size.
575 full_size_ = ClampSize(full_size_);
576 }
577
SetAutoResizable(bool resizable)578 void Panel::SetAutoResizable(bool resizable) {
579 if (auto_resizable_ == resizable)
580 return;
581
582 auto_resizable_ = resizable;
583 content::WebContents* web_contents = GetWebContents();
584 if (auto_resizable_) {
585 if (web_contents)
586 EnableWebContentsAutoResize(web_contents);
587 } else {
588 if (web_contents) {
589 registrar_.Remove(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
590 content::Source<content::WebContents>(web_contents));
591
592 // NULL might be returned if the tab has not been added.
593 RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
594 if (render_view_host)
595 render_view_host->DisableAutoResize(full_size_);
596 }
597 }
598 }
599
EnableWebContentsAutoResize(content::WebContents * web_contents)600 void Panel::EnableWebContentsAutoResize(content::WebContents* web_contents) {
601 DCHECK(web_contents);
602 ConfigureAutoResize(web_contents);
603
604 // We also need to know when the render view host changes in order
605 // to turn on auto-resize notifications in the new render view host.
606 if (!registrar_.IsRegistered(
607 this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
608 content::Source<content::WebContents>(web_contents))) {
609 registrar_.Add(
610 this,
611 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
612 content::Source<content::WebContents>(web_contents));
613 }
614 }
615
OnContentsAutoResized(const gfx::Size & new_content_size)616 void Panel::OnContentsAutoResized(const gfx::Size& new_content_size) {
617 DCHECK(auto_resizable_);
618 if (!collection_)
619 return;
620
621 gfx::Size new_window_size =
622 native_panel_->WindowSizeFromContentSize(new_content_size);
623
624 // Ignore content auto resizes until window frame size is known.
625 // This reduces extra resizes when panel is first shown.
626 // After window frame size is known, it will trigger another content
627 // auto resize.
628 if (new_content_size == new_window_size)
629 return;
630
631 collection_->ResizePanelWindow(this, new_window_size);
632 }
633
OnWindowResizedByMouse(const gfx::Rect & new_bounds)634 void Panel::OnWindowResizedByMouse(const gfx::Rect& new_bounds) {
635 if (collection_)
636 collection_->OnPanelResizedByMouse(this, new_bounds);
637 }
638
SetSizeRange(const gfx::Size & min_size,const gfx::Size & max_size)639 void Panel::SetSizeRange(const gfx::Size& min_size, const gfx::Size& max_size) {
640 if (min_size == min_size_ && max_size == max_size_)
641 return;
642
643 DCHECK(min_size.width() <= max_size.width());
644 DCHECK(min_size.height() <= max_size.height());
645 min_size_ = min_size;
646 max_size_ = max_size;
647
648 ConfigureAutoResize(GetWebContents());
649 }
650
IncreaseMaxSize(const gfx::Size & desired_panel_size)651 void Panel::IncreaseMaxSize(const gfx::Size& desired_panel_size) {
652 gfx::Size new_max_size = max_size_;
653 if (new_max_size.width() < desired_panel_size.width())
654 new_max_size.set_width(desired_panel_size.width());
655 if (new_max_size.height() < desired_panel_size.height())
656 new_max_size.set_height(desired_panel_size.height());
657
658 SetSizeRange(min_size_, new_max_size);
659 }
660
HandleKeyboardEvent(const content::NativeWebKeyboardEvent & event)661 void Panel::HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
662 native_panel_->HandlePanelKeyboardEvent(event);
663 }
664
SetPreviewMode(bool in_preview)665 void Panel::SetPreviewMode(bool in_preview) {
666 DCHECK_NE(in_preview_mode_, in_preview);
667 in_preview_mode_ = in_preview;
668 }
669
UpdateMinimizeRestoreButtonVisibility()670 void Panel::UpdateMinimizeRestoreButtonVisibility() {
671 native_panel_->UpdatePanelMinimizeRestoreButtonVisibility();
672 }
673
ClampSize(const gfx::Size & size) const674 gfx::Size Panel::ClampSize(const gfx::Size& size) const {
675 // The panel width:
676 // * cannot grow or shrink to go beyond [min_width, max_width]
677 int new_width = size.width();
678 if (new_width > max_size_.width())
679 new_width = max_size_.width();
680 if (new_width < min_size_.width())
681 new_width = min_size_.width();
682
683 // The panel height:
684 // * cannot grow or shrink to go beyond [min_height, max_height]
685 int new_height = size.height();
686 if (new_height > max_size_.height())
687 new_height = max_size_.height();
688 if (new_height < min_size_.height())
689 new_height = min_size_.height();
690
691 return gfx::Size(new_width, new_height);
692 }
693
OnActiveStateChanged(bool active)694 void Panel::OnActiveStateChanged(bool active) {
695 // Clear attention state when an expanded panel becomes active.
696 // On some systems (e.g. Win), mouse-down activates a panel regardless of
697 // its expansion state. However, we don't want to clear draw attention if
698 // contents are not visible. In that scenario, if the mouse-down results
699 // in a mouse-click, draw attention will be cleared then.
700 // See Panel::OnTitlebarClicked().
701 if (active && IsDrawingAttention() && !IsMinimized())
702 FlashFrame(false);
703
704 if (collection_)
705 collection_->OnPanelActiveStateChanged(this);
706
707 // Send extension event about window changing active state.
708 extensions::TabsWindowsAPI* tabs_windows_api =
709 extensions::TabsWindowsAPI::Get(profile());
710 if (tabs_windows_api) {
711 tabs_windows_api->windows_event_router()->OnActiveWindowChanged(
712 active ? extension_window_controller_.get() : NULL);
713 }
714
715 content::NotificationService::current()->Notify(
716 chrome::NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
717 content::Source<Panel>(this),
718 content::NotificationService::NoDetails());
719 }
720
OnPanelStartUserResizing()721 void Panel::OnPanelStartUserResizing() {
722 SetAutoResizable(false);
723 SetPreviewMode(true);
724 max_size_policy_ = CUSTOM_MAX_SIZE;
725 }
726
OnPanelEndUserResizing()727 void Panel::OnPanelEndUserResizing() {
728 SetPreviewMode(false);
729 }
730
ShouldCloseWindow()731 bool Panel::ShouldCloseWindow() {
732 return true;
733 }
734
OnWindowClosing()735 void Panel::OnWindowClosing() {
736 if (GetWebContents()) {
737 native_panel_->DetachWebContents(GetWebContents());
738 panel_host_->DestroyWebContents();
739 }
740 }
741
ExecuteCommandIfEnabled(int id)742 bool Panel::ExecuteCommandIfEnabled(int id) {
743 if (command_updater()->SupportsCommand(id) &&
744 command_updater()->IsCommandEnabled(id)) {
745 ExecuteCommandWithDisposition(id, CURRENT_TAB);
746 return true;
747 }
748 return false;
749 }
750
GetWindowTitle() const751 base::string16 Panel::GetWindowTitle() const {
752 content::WebContents* contents = GetWebContents();
753 base::string16 title;
754
755 // |contents| can be NULL during the window's creation.
756 if (contents) {
757 title = contents->GetTitle();
758 FormatTitleForDisplay(&title);
759 }
760
761 if (title.empty())
762 title = UTF8ToUTF16(app_name());
763
764 return title;
765 }
766
GetCurrentPageIcon() const767 gfx::Image Panel::GetCurrentPageIcon() const {
768 return panel_host_->GetPageIcon();
769 }
770
UpdateTitleBar()771 void Panel::UpdateTitleBar() {
772 native_panel_->UpdatePanelTitleBar();
773 }
774
LoadingStateChanged(bool is_loading)775 void Panel::LoadingStateChanged(bool is_loading) {
776 command_updater_.UpdateCommandEnabled(IDC_STOP, is_loading);
777 native_panel_->UpdatePanelLoadingAnimations(is_loading);
778 UpdateTitleBar();
779 }
780
WebContentsFocused(content::WebContents * contents)781 void Panel::WebContentsFocused(content::WebContents* contents) {
782 native_panel_->PanelWebContentsFocused(contents);
783 }
784
MoveByInstantly(const gfx::Vector2d & delta_origin)785 void Panel::MoveByInstantly(const gfx::Vector2d& delta_origin) {
786 gfx::Rect bounds = GetBounds();
787 bounds.Offset(delta_origin);
788 SetPanelBoundsInstantly(bounds);
789 }
790
SetWindowCornerStyle(panel::CornerStyle corner_style)791 void Panel::SetWindowCornerStyle(panel::CornerStyle corner_style) {
792 native_panel_->SetWindowCornerStyle(corner_style);
793 }
794
MinimizeBySystem()795 void Panel::MinimizeBySystem() {
796 native_panel_->MinimizePanelBySystem();
797 }
798
Panel(Profile * profile,const std::string & app_name,const gfx::Size & min_size,const gfx::Size & max_size)799 Panel::Panel(Profile* profile, const std::string& app_name,
800 const gfx::Size& min_size, const gfx::Size& max_size)
801 : app_name_(app_name),
802 profile_(profile),
803 collection_(NULL),
804 initialized_(false),
805 min_size_(min_size),
806 max_size_(max_size),
807 max_size_policy_(DEFAULT_MAX_SIZE),
808 auto_resizable_(false),
809 in_preview_mode_(false),
810 native_panel_(NULL),
811 attention_mode_(USE_PANEL_ATTENTION),
812 expansion_state_(EXPANDED),
813 command_updater_(this),
814 image_loader_ptr_factory_(this) {
815 }
816
OnImageLoaded(const gfx::Image & image)817 void Panel::OnImageLoaded(const gfx::Image& image) {
818 if (!image.IsEmpty()) {
819 app_icon_ = image;
820 native_panel_->UpdatePanelTitleBar();
821 }
822
823 content::NotificationService::current()->Notify(
824 chrome::NOTIFICATION_PANEL_APP_ICON_LOADED,
825 content::Source<Panel>(this),
826 content::NotificationService::NoDetails());
827 }
828
InitCommandState()829 void Panel::InitCommandState() {
830 // All supported commands whose state isn't set automagically some other way
831 // (like Stop during a page load) must have their state initialized here,
832 // otherwise they will be forever disabled.
833
834 // Navigation commands
835 command_updater_.UpdateCommandEnabled(IDC_RELOAD, true);
836 command_updater_.UpdateCommandEnabled(IDC_RELOAD_IGNORING_CACHE, true);
837
838 // Window management commands
839 command_updater_.UpdateCommandEnabled(IDC_CLOSE_WINDOW, true);
840 command_updater_.UpdateCommandEnabled(IDC_EXIT, true);
841
842 // Zoom
843 command_updater_.UpdateCommandEnabled(IDC_ZOOM_MENU, true);
844 command_updater_.UpdateCommandEnabled(IDC_ZOOM_PLUS, true);
845 command_updater_.UpdateCommandEnabled(IDC_ZOOM_NORMAL, true);
846 command_updater_.UpdateCommandEnabled(IDC_ZOOM_MINUS, true);
847
848 // Clipboard
849 command_updater_.UpdateCommandEnabled(IDC_COPY, true);
850 command_updater_.UpdateCommandEnabled(IDC_CUT, true);
851 command_updater_.UpdateCommandEnabled(IDC_PASTE, true);
852
853 // DevTools
854 command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS, true);
855 command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS_CONSOLE, true);
856 }
857
ConfigureAutoResize(content::WebContents * web_contents)858 void Panel::ConfigureAutoResize(content::WebContents* web_contents) {
859 if (!auto_resizable_ || !web_contents)
860 return;
861
862 // NULL might be returned if the tab has not been added.
863 RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
864 if (!render_view_host)
865 return;
866
867 render_view_host->EnableAutoResize(
868 min_size_,
869 native_panel_->ContentSizeFromWindowSize(max_size_));
870 }
871
UpdateAppIcon()872 void Panel::UpdateAppIcon() {
873 const extensions::Extension* extension = GetExtension();
874 if (!extension)
875 return;
876
877 extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile());
878 loader->LoadImageAsync(
879 extension,
880 extensions::IconsInfo::GetIconResource(
881 extension,
882 extension_misc::EXTENSION_ICON_SMALL,
883 ExtensionIconSet::MATCH_BIGGER),
884 gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
885 extension_misc::EXTENSION_ICON_SMALL),
886 base::Bind(&Panel::OnImageLoaded,
887 image_loader_ptr_factory_.GetWeakPtr()));
888 }
889
890 // static
FormatTitleForDisplay(base::string16 * title)891 void Panel::FormatTitleForDisplay(base::string16* title) {
892 size_t current_index = 0;
893 size_t match_index;
894 while ((match_index = title->find(L'\n', current_index)) !=
895 base::string16::npos) {
896 title->replace(match_index, 1, base::string16());
897 current_index = match_index;
898 }
899 }
900