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 } 910