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