1 // Copyright 2013 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 "apps/shell_window.h" 6 7 #include "apps/shell_window_geometry_cache.h" 8 #include "apps/shell_window_registry.h" 9 #include "apps/ui/native_app_window.h" 10 #include "base/command_line.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/values.h" 14 #include "chrome/browser/chrome_notification_types.h" 15 #include "chrome/browser/extensions/extension_system.h" 16 #include "chrome/browser/extensions/extension_web_contents_observer.h" 17 #include "chrome/browser/extensions/suggest_permission_util.h" 18 #include "chrome/browser/lifetime/application_lifetime.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/common/chrome_switches.h" 21 #include "chrome/common/extensions/extension_messages.h" 22 #include "chrome/common/extensions/manifest_handlers/icons_handler.h" 23 #include "components/web_modal/web_contents_modal_dialog_manager.h" 24 #include "content/public/browser/invalidate_type.h" 25 #include "content/public/browser/navigation_entry.h" 26 #include "content/public/browser/notification_details.h" 27 #include "content/public/browser/notification_service.h" 28 #include "content/public/browser/notification_source.h" 29 #include "content/public/browser/notification_types.h" 30 #include "content/public/browser/render_view_host.h" 31 #include "content/public/browser/resource_dispatcher_host.h" 32 #include "content/public/browser/web_contents.h" 33 #include "content/public/browser/web_contents_view.h" 34 #include "content/public/common/media_stream_request.h" 35 #include "extensions/browser/process_manager.h" 36 #include "extensions/browser/view_type_utils.h" 37 #include "extensions/common/extension.h" 38 #include "third_party/skia/include/core/SkRegion.h" 39 #include "ui/gfx/screen.h" 40 41 #if !defined(OS_MACOSX) 42 #include "apps/pref_names.h" 43 #include "base/prefs/pref_service.h" 44 #endif 45 46 using content::ConsoleMessageLevel; 47 using content::WebContents; 48 using extensions::APIPermission; 49 using web_modal::WebContentsModalDialogHost; 50 using web_modal::WebContentsModalDialogManager; 51 52 namespace { 53 54 const int kDefaultWidth = 512; 55 const int kDefaultHeight = 384; 56 57 } // namespace 58 59 namespace apps { 60 61 ShellWindow::SizeConstraints::SizeConstraints() 62 : maximum_size_(kUnboundedSize, kUnboundedSize) { 63 } 64 65 ShellWindow::SizeConstraints::SizeConstraints(const gfx::Size& min_size, 66 const gfx::Size& max_size) 67 : minimum_size_(min_size), 68 maximum_size_(max_size) { 69 } 70 71 ShellWindow::SizeConstraints::~SizeConstraints() {} 72 73 gfx::Size ShellWindow::SizeConstraints::ClampSize(gfx::Size size) const { 74 const gfx::Size max_size = GetMaximumSize(); 75 if (max_size.width() != kUnboundedSize) 76 size.set_width(std::min(size.width(), GetMaximumSize().width())); 77 if (max_size.height() != kUnboundedSize) 78 size.set_height(std::min(size.height(), GetMaximumSize().height())); 79 size.SetToMax(GetMinimumSize()); 80 return size; 81 } 82 83 bool ShellWindow::SizeConstraints::HasMinimumSize() const { 84 return GetMinimumSize().width() != kUnboundedSize || 85 GetMinimumSize().height() != kUnboundedSize; 86 } 87 88 bool ShellWindow::SizeConstraints::HasMaximumSize() const { 89 const gfx::Size max_size = GetMaximumSize(); 90 return max_size.width() != kUnboundedSize || 91 max_size.height() != kUnboundedSize; 92 } 93 94 bool ShellWindow::SizeConstraints::HasFixedSize() const { 95 return !GetMinimumSize().IsEmpty() && GetMinimumSize() == GetMaximumSize(); 96 } 97 98 gfx::Size ShellWindow::SizeConstraints::GetMinimumSize() const { 99 return minimum_size_; 100 } 101 102 gfx::Size ShellWindow::SizeConstraints::GetMaximumSize() const { 103 return gfx::Size( 104 maximum_size_.width() == kUnboundedSize ? 105 kUnboundedSize : 106 std::max(maximum_size_.width(), minimum_size_.width()), 107 maximum_size_.height() == kUnboundedSize ? 108 kUnboundedSize : 109 std::max(maximum_size_.height(), minimum_size_.height())); 110 } 111 112 void ShellWindow::SizeConstraints::set_minimum_size(const gfx::Size& min_size) { 113 minimum_size_ = min_size; 114 } 115 116 void ShellWindow::SizeConstraints::set_maximum_size(const gfx::Size& max_size) { 117 maximum_size_ = max_size; 118 } 119 120 ShellWindow::CreateParams::CreateParams() 121 : window_type(ShellWindow::WINDOW_TYPE_DEFAULT), 122 frame(ShellWindow::FRAME_CHROME), 123 transparent_background(false), 124 bounds(INT_MIN, INT_MIN, 0, 0), 125 creator_process_id(0), 126 state(ui::SHOW_STATE_DEFAULT), 127 hidden(false), 128 resizable(true), 129 focused(true), 130 always_on_top(false) {} 131 132 ShellWindow::CreateParams::~CreateParams() {} 133 134 ShellWindow::Delegate::~Delegate() {} 135 136 ShellWindow::ShellWindow(Profile* profile, 137 Delegate* delegate, 138 const extensions::Extension* extension) 139 : profile_(profile), 140 extension_(extension), 141 extension_id_(extension->id()), 142 window_type_(WINDOW_TYPE_DEFAULT), 143 delegate_(delegate), 144 image_loader_ptr_factory_(this), 145 fullscreen_types_(FULLSCREEN_TYPE_NONE), 146 show_on_first_paint_(false), 147 first_paint_complete_(false), 148 cached_always_on_top_(false) { 149 CHECK(!profile->IsGuestSession() || profile->IsOffTheRecord()) 150 << "Only off the record window may be opened in the guest mode."; 151 } 152 153 void ShellWindow::Init(const GURL& url, 154 ShellWindowContents* shell_window_contents, 155 const CreateParams& params) { 156 // Initialize the render interface and web contents 157 shell_window_contents_.reset(shell_window_contents); 158 shell_window_contents_->Initialize(profile(), url); 159 WebContents* web_contents = shell_window_contents_->GetWebContents(); 160 if (CommandLine::ForCurrentProcess()->HasSwitch( 161 switches::kEnableAppsShowOnFirstPaint)) { 162 content::WebContentsObserver::Observe(web_contents); 163 } 164 delegate_->InitWebContents(web_contents); 165 WebContentsModalDialogManager::CreateForWebContents(web_contents); 166 extensions::ExtensionWebContentsObserver::CreateForWebContents(web_contents); 167 168 web_contents->SetDelegate(this); 169 WebContentsModalDialogManager::FromWebContents(web_contents)-> 170 SetDelegate(this); 171 extensions::SetViewType(web_contents, extensions::VIEW_TYPE_APP_SHELL); 172 173 // Initialize the window 174 CreateParams new_params = LoadDefaultsAndConstrain(params); 175 window_type_ = new_params.window_type; 176 window_key_ = new_params.window_key; 177 size_constraints_ = SizeConstraints(new_params.minimum_size, 178 new_params.maximum_size); 179 180 // Windows cannot be always-on-top in fullscreen mode for security reasons. 181 cached_always_on_top_ = new_params.always_on_top; 182 if (new_params.state == ui::SHOW_STATE_FULLSCREEN) 183 new_params.always_on_top = false; 184 185 native_app_window_.reset(delegate_->CreateNativeAppWindow(this, new_params)); 186 187 if (!new_params.hidden) { 188 // Panels are not activated by default. 189 Show(window_type_is_panel() || !new_params.focused ? SHOW_INACTIVE 190 : SHOW_ACTIVE); 191 } 192 193 if (new_params.state == ui::SHOW_STATE_FULLSCREEN) 194 Fullscreen(); 195 else if (new_params.state == ui::SHOW_STATE_MAXIMIZED) 196 Maximize(); 197 else if (new_params.state == ui::SHOW_STATE_MINIMIZED) 198 Minimize(); 199 200 OnNativeWindowChanged(); 201 202 // When the render view host is changed, the native window needs to know 203 // about it in case it has any setup to do to make the renderer appear 204 // properly. In particular, on Windows, the view's clickthrough region needs 205 // to be set. 206 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 207 content::Source<Profile>(profile_)); 208 // Close when the browser process is exiting. 209 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, 210 content::NotificationService::AllSources()); 211 212 shell_window_contents_->LoadContents(new_params.creator_process_id); 213 214 if (CommandLine::ForCurrentProcess()->HasSwitch( 215 switches::kEnableAppsShowOnFirstPaint)) { 216 // We want to show the window only when the content has been painted. For 217 // that to happen, we need to define a size for the content, otherwise the 218 // layout will happen in a 0x0 area. 219 // Note: WebContents::GetView() is guaranteed to be non-null. 220 web_contents->GetView()->SizeContents(new_params.bounds.size()); 221 } 222 223 // Prevent the browser process from shutting down while this window is open. 224 chrome::StartKeepAlive(); 225 226 UpdateExtensionAppIcon(); 227 228 ShellWindowRegistry::Get(profile_)->AddShellWindow(this); 229 } 230 231 ShellWindow::~ShellWindow() { 232 // Unregister now to prevent getting NOTIFICATION_APP_TERMINATING if we're the 233 // last window open. 234 registrar_.RemoveAll(); 235 236 // Remove shutdown prevention. 237 chrome::EndKeepAlive(); 238 } 239 240 void ShellWindow::RequestMediaAccessPermission( 241 content::WebContents* web_contents, 242 const content::MediaStreamRequest& request, 243 const content::MediaResponseCallback& callback) { 244 delegate_->RequestMediaAccessPermission(web_contents, request, callback, 245 extension()); 246 } 247 248 WebContents* ShellWindow::OpenURLFromTab(WebContents* source, 249 const content::OpenURLParams& params) { 250 // Don't allow the current tab to be navigated. It would be nice to map all 251 // anchor tags (even those without target="_blank") to new tabs, but right 252 // now we can't distinguish between those and <meta> refreshes or window.href 253 // navigations, which we don't want to allow. 254 // TOOD(mihaip): Can we check for user gestures instead? 255 WindowOpenDisposition disposition = params.disposition; 256 if (disposition == CURRENT_TAB) { 257 AddMessageToDevToolsConsole( 258 content::CONSOLE_MESSAGE_LEVEL_ERROR, 259 base::StringPrintf( 260 "Can't open same-window link to \"%s\"; try target=\"_blank\".", 261 params.url.spec().c_str())); 262 return NULL; 263 } 264 265 // These dispositions aren't really navigations. 266 if (disposition == SUPPRESS_OPEN || disposition == SAVE_TO_DISK || 267 disposition == IGNORE_ACTION) { 268 return NULL; 269 } 270 271 WebContents* contents = delegate_->OpenURLFromTab(profile_, source, 272 params); 273 if (!contents) { 274 AddMessageToDevToolsConsole( 275 content::CONSOLE_MESSAGE_LEVEL_ERROR, 276 base::StringPrintf( 277 "Can't navigate to \"%s\"; apps do not support navigation.", 278 params.url.spec().c_str())); 279 } 280 281 return contents; 282 } 283 284 void ShellWindow::AddNewContents(WebContents* source, 285 WebContents* new_contents, 286 WindowOpenDisposition disposition, 287 const gfx::Rect& initial_pos, 288 bool user_gesture, 289 bool* was_blocked) { 290 DCHECK(Profile::FromBrowserContext(new_contents->GetBrowserContext()) == 291 profile_); 292 delegate_->AddNewContents(profile_, new_contents, disposition, 293 initial_pos, user_gesture, was_blocked); 294 } 295 296 bool ShellWindow::PreHandleKeyboardEvent( 297 content::WebContents* source, 298 const content::NativeWebKeyboardEvent& event, 299 bool* is_keyboard_shortcut) { 300 // Here, we can handle a key event before the content gets it. When we are 301 // fullscreen and it is not forced, we want to allow the user to leave 302 // when ESC is pressed. 303 // However, if the application has the "overrideEscFullscreen" permission, we 304 // should let it override that behavior. 305 // ::HandleKeyboardEvent() will only be called if the KeyEvent's default 306 // action is not prevented. 307 // Thus, we should handle the KeyEvent here only if the permission is not set. 308 if (event.windowsKeyCode == ui::VKEY_ESCAPE && 309 (fullscreen_types_ != FULLSCREEN_TYPE_NONE) && 310 ((fullscreen_types_ & FULLSCREEN_TYPE_FORCED) == 0) && 311 !extension_->HasAPIPermission(APIPermission::kOverrideEscFullscreen)) { 312 Restore(); 313 return true; 314 } 315 316 return false; 317 } 318 319 void ShellWindow::HandleKeyboardEvent( 320 WebContents* source, 321 const content::NativeWebKeyboardEvent& event) { 322 // If the window is currently fullscreen and not forced, ESC should leave 323 // fullscreen. If this code is being called for ESC, that means that the 324 // KeyEvent's default behavior was not prevented by the content. 325 if (event.windowsKeyCode == ui::VKEY_ESCAPE && 326 (fullscreen_types_ != FULLSCREEN_TYPE_NONE) && 327 ((fullscreen_types_ & FULLSCREEN_TYPE_FORCED) == 0)) { 328 Restore(); 329 return; 330 } 331 332 native_app_window_->HandleKeyboardEvent(event); 333 } 334 335 void ShellWindow::RequestToLockMouse(WebContents* web_contents, 336 bool user_gesture, 337 bool last_unlocked_by_target) { 338 bool has_permission = IsExtensionWithPermissionOrSuggestInConsole( 339 APIPermission::kPointerLock, 340 extension_, 341 web_contents->GetRenderViewHost()); 342 343 web_contents->GotResponseToLockMouseRequest(has_permission); 344 } 345 346 void ShellWindow::DidFirstVisuallyNonEmptyPaint(int32 page_id) { 347 first_paint_complete_ = true; 348 if (show_on_first_paint_) { 349 DCHECK(delayed_show_type_ == SHOW_ACTIVE || 350 delayed_show_type_ == SHOW_INACTIVE); 351 Show(delayed_show_type_); 352 } 353 } 354 355 void ShellWindow::OnNativeClose() { 356 ShellWindowRegistry::Get(profile_)->RemoveShellWindow(this); 357 if (shell_window_contents_) { 358 WebContents* web_contents = shell_window_contents_->GetWebContents(); 359 WebContentsModalDialogManager::FromWebContents(web_contents)-> 360 SetDelegate(NULL); 361 shell_window_contents_->NativeWindowClosed(); 362 } 363 delete this; 364 } 365 366 void ShellWindow::OnNativeWindowChanged() { 367 SaveWindowPosition(); 368 if (shell_window_contents_ && native_app_window_) 369 shell_window_contents_->NativeWindowChanged(native_app_window_.get()); 370 } 371 372 void ShellWindow::OnNativeWindowActivated() { 373 ShellWindowRegistry::Get(profile_)->ShellWindowActivated(this); 374 } 375 376 content::WebContents* ShellWindow::web_contents() const { 377 return shell_window_contents_->GetWebContents(); 378 } 379 380 NativeAppWindow* ShellWindow::GetBaseWindow() { 381 return native_app_window_.get(); 382 } 383 384 gfx::NativeWindow ShellWindow::GetNativeWindow() { 385 return GetBaseWindow()->GetNativeWindow(); 386 } 387 388 gfx::Rect ShellWindow::GetClientBounds() const { 389 gfx::Rect bounds = native_app_window_->GetBounds(); 390 bounds.Inset(native_app_window_->GetFrameInsets()); 391 return bounds; 392 } 393 394 base::string16 ShellWindow::GetTitle() const { 395 // WebContents::GetTitle() will return the page's URL if there's no <title> 396 // specified. However, we'd prefer to show the name of the extension in that 397 // case, so we directly inspect the NavigationEntry's title. 398 base::string16 title; 399 if (!web_contents() || 400 !web_contents()->GetController().GetActiveEntry() || 401 web_contents()->GetController().GetActiveEntry()->GetTitle().empty()) { 402 title = UTF8ToUTF16(extension()->name()); 403 } else { 404 title = web_contents()->GetTitle(); 405 } 406 const base::char16 kBadChars[] = { '\n', 0 }; 407 base::RemoveChars(title, kBadChars, &title); 408 return title; 409 } 410 411 void ShellWindow::SetAppIconUrl(const GURL& url) { 412 // Avoid using any previous app icons were are being downloaded. 413 image_loader_ptr_factory_.InvalidateWeakPtrs(); 414 415 // Reset |app_icon_image_| to abort pending image load (if any). 416 app_icon_image_.reset(); 417 418 app_icon_url_ = url; 419 web_contents()->DownloadImage( 420 url, 421 true, // is a favicon 422 0, // no maximum size 423 base::Bind(&ShellWindow::DidDownloadFavicon, 424 image_loader_ptr_factory_.GetWeakPtr())); 425 } 426 427 void ShellWindow::UpdateShape(scoped_ptr<SkRegion> region) { 428 native_app_window_->UpdateShape(region.Pass()); 429 } 430 431 void ShellWindow::UpdateDraggableRegions( 432 const std::vector<extensions::DraggableRegion>& regions) { 433 native_app_window_->UpdateDraggableRegions(regions); 434 } 435 436 void ShellWindow::UpdateAppIcon(const gfx::Image& image) { 437 if (image.IsEmpty()) 438 return; 439 app_icon_ = image; 440 native_app_window_->UpdateWindowIcon(); 441 ShellWindowRegistry::Get(profile_)->ShellWindowIconChanged(this); 442 } 443 444 void ShellWindow::Fullscreen() { 445 #if !defined(OS_MACOSX) 446 // Do not enter fullscreen mode if disallowed by pref. 447 if (!profile()->GetPrefs()->GetBoolean(prefs::kAppFullscreenAllowed)) 448 return; 449 #endif 450 fullscreen_types_ |= FULLSCREEN_TYPE_WINDOW_API; 451 SetNativeWindowFullscreen(fullscreen_types_); 452 } 453 454 void ShellWindow::Maximize() { 455 GetBaseWindow()->Maximize(); 456 } 457 458 void ShellWindow::Minimize() { 459 GetBaseWindow()->Minimize(); 460 } 461 462 void ShellWindow::Restore() { 463 if (fullscreen_types_ != FULLSCREEN_TYPE_NONE) { 464 fullscreen_types_ = FULLSCREEN_TYPE_NONE; 465 SetNativeWindowFullscreen(fullscreen_types_); 466 } else { 467 GetBaseWindow()->Restore(); 468 } 469 } 470 471 void ShellWindow::OSFullscreen() { 472 #if !defined(OS_MACOSX) 473 // Do not enter fullscreen mode if disallowed by pref. 474 if (!profile()->GetPrefs()->GetBoolean(prefs::kAppFullscreenAllowed)) 475 return; 476 #endif 477 fullscreen_types_ |= FULLSCREEN_TYPE_OS; 478 SetNativeWindowFullscreen(fullscreen_types_); 479 } 480 481 void ShellWindow::ForcedFullscreen() { 482 fullscreen_types_ |= FULLSCREEN_TYPE_FORCED; 483 SetNativeWindowFullscreen(fullscreen_types_); 484 } 485 486 void ShellWindow::SetMinimumSize(const gfx::Size& min_size) { 487 size_constraints_.set_minimum_size(min_size); 488 OnSizeConstraintsChanged(); 489 } 490 491 void ShellWindow::SetMaximumSize(const gfx::Size& max_size) { 492 size_constraints_.set_maximum_size(max_size); 493 OnSizeConstraintsChanged(); 494 } 495 496 void ShellWindow::Show(ShowType show_type) { 497 if (CommandLine::ForCurrentProcess()->HasSwitch( 498 switches::kEnableAppsShowOnFirstPaint)) { 499 show_on_first_paint_ = true; 500 501 if (!first_paint_complete_) { 502 delayed_show_type_ = show_type; 503 return; 504 } 505 } 506 507 switch (show_type) { 508 case SHOW_ACTIVE: 509 GetBaseWindow()->Show(); 510 break; 511 case SHOW_INACTIVE: 512 GetBaseWindow()->ShowInactive(); 513 break; 514 } 515 } 516 517 void ShellWindow::Hide() { 518 // This is there to prevent race conditions with Hide() being called before 519 // there was a non-empty paint. It should have no effect in a non-racy 520 // scenario where the application is hiding then showing a window: the second 521 // show will not be delayed. 522 show_on_first_paint_ = false; 523 GetBaseWindow()->Hide(); 524 } 525 526 void ShellWindow::SetAlwaysOnTop(bool always_on_top) { 527 if (cached_always_on_top_ == always_on_top) 528 return; 529 530 cached_always_on_top_ = always_on_top; 531 532 // As a security measure, do not allow fullscreen windows to be on top. 533 // The property will be applied when the window exits fullscreen. 534 bool fullscreen = (fullscreen_types_ != FULLSCREEN_TYPE_NONE); 535 if (!fullscreen) 536 native_app_window_->SetAlwaysOnTop(always_on_top); 537 538 OnNativeWindowChanged(); 539 } 540 541 bool ShellWindow::IsAlwaysOnTop() const { 542 return cached_always_on_top_; 543 } 544 545 //------------------------------------------------------------------------------ 546 // Private methods 547 548 void ShellWindow::DidDownloadFavicon( 549 int id, 550 int http_status_code, 551 const GURL& image_url, 552 const std::vector<SkBitmap>& bitmaps, 553 const std::vector<gfx::Size>& original_bitmap_sizes) { 554 if (image_url != app_icon_url_ || bitmaps.empty()) 555 return; 556 557 // Bitmaps are ordered largest to smallest. Choose the smallest bitmap 558 // whose height >= the preferred size. 559 int largest_index = 0; 560 for (size_t i = 1; i < bitmaps.size(); ++i) { 561 if (bitmaps[i].height() < delegate_->PreferredIconSize()) 562 break; 563 largest_index = i; 564 } 565 const SkBitmap& largest = bitmaps[largest_index]; 566 UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest)); 567 } 568 569 void ShellWindow::OnExtensionIconImageChanged(extensions::IconImage* image) { 570 DCHECK_EQ(app_icon_image_.get(), image); 571 572 UpdateAppIcon(gfx::Image(app_icon_image_->image_skia())); 573 } 574 575 void ShellWindow::UpdateExtensionAppIcon() { 576 // Avoid using any previous app icons were are being downloaded. 577 image_loader_ptr_factory_.InvalidateWeakPtrs(); 578 579 app_icon_image_.reset(new extensions::IconImage( 580 profile(), 581 extension(), 582 extensions::IconsInfo::GetIcons(extension()), 583 delegate_->PreferredIconSize(), 584 extensions::IconsInfo::GetDefaultAppIcon(), 585 this)); 586 587 // Triggers actual image loading with 1x resources. The 2x resource will 588 // be handled by IconImage class when requested. 589 app_icon_image_->image_skia().GetRepresentation(1.0f); 590 } 591 592 void ShellWindow::OnSizeConstraintsChanged() { 593 native_app_window_->UpdateWindowMinMaxSize(); 594 gfx::Rect bounds = GetClientBounds(); 595 gfx::Size constrained_size = size_constraints_.ClampSize(bounds.size()); 596 if (bounds.size() != constrained_size) { 597 bounds.set_size(constrained_size); 598 native_app_window_->SetBounds(bounds); 599 } 600 OnNativeWindowChanged(); 601 } 602 603 void ShellWindow::SetNativeWindowFullscreen(int fullscreen_types) { 604 native_app_window_->SetFullscreen(fullscreen_types); 605 606 if (!cached_always_on_top_) 607 return; 608 609 bool is_on_top = native_app_window_->IsAlwaysOnTop(); 610 bool fullscreen = (fullscreen_types != FULLSCREEN_TYPE_NONE); 611 if (fullscreen && is_on_top) { 612 // When entering fullscreen, ensure windows are not always-on-top. 613 native_app_window_->SetAlwaysOnTop(false); 614 } else if (!fullscreen && !is_on_top) { 615 // When exiting fullscreen, reinstate always-on-top. 616 native_app_window_->SetAlwaysOnTop(true); 617 } 618 } 619 620 void ShellWindow::CloseContents(WebContents* contents) { 621 native_app_window_->Close(); 622 } 623 624 bool ShellWindow::ShouldSuppressDialogs() { 625 return true; 626 } 627 628 content::ColorChooser* ShellWindow::OpenColorChooser( 629 WebContents* web_contents, 630 SkColor initial_color, 631 const std::vector<content::ColorSuggestion>& suggestionss) { 632 return delegate_->ShowColorChooser(web_contents, initial_color); 633 } 634 635 void ShellWindow::RunFileChooser(WebContents* tab, 636 const content::FileChooserParams& params) { 637 if (window_type_is_panel()) { 638 // Panels can't host a file dialog, abort. TODO(stevenjb): allow file 639 // dialogs to be unhosted but still close with the owning web contents. 640 // crbug.com/172502. 641 LOG(WARNING) << "File dialog opened by panel."; 642 return; 643 } 644 645 delegate_->RunFileChooser(tab, params); 646 } 647 648 bool ShellWindow::IsPopupOrPanel(const WebContents* source) const { 649 return true; 650 } 651 652 void ShellWindow::MoveContents(WebContents* source, const gfx::Rect& pos) { 653 native_app_window_->SetBounds(pos); 654 } 655 656 void ShellWindow::NavigationStateChanged( 657 const content::WebContents* source, unsigned changed_flags) { 658 if (changed_flags & content::INVALIDATE_TYPE_TITLE) 659 native_app_window_->UpdateWindowTitle(); 660 else if (changed_flags & content::INVALIDATE_TYPE_TAB) 661 native_app_window_->UpdateWindowIcon(); 662 } 663 664 void ShellWindow::ToggleFullscreenModeForTab(content::WebContents* source, 665 bool enter_fullscreen) { 666 #if !defined(OS_MACOSX) 667 // Do not enter fullscreen mode if disallowed by pref. 668 // TODO(bartfab): Add a test once it becomes possible to simulate a user 669 // gesture. http://crbug.com/174178 670 if (enter_fullscreen && 671 !profile()->GetPrefs()->GetBoolean(prefs::kAppFullscreenAllowed)) { 672 return; 673 } 674 #endif 675 676 if (!IsExtensionWithPermissionOrSuggestInConsole( 677 APIPermission::kFullscreen, 678 extension_, 679 source->GetRenderViewHost())) { 680 return; 681 } 682 683 if (enter_fullscreen) 684 fullscreen_types_ |= FULLSCREEN_TYPE_HTML_API; 685 else 686 fullscreen_types_ &= ~FULLSCREEN_TYPE_HTML_API; 687 SetNativeWindowFullscreen(fullscreen_types_); 688 } 689 690 bool ShellWindow::IsFullscreenForTabOrPending( 691 const content::WebContents* source) const { 692 return ((fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0); 693 } 694 695 void ShellWindow::Observe(int type, 696 const content::NotificationSource& source, 697 const content::NotificationDetails& details) { 698 switch (type) { 699 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 700 const extensions::Extension* unloaded_extension = 701 content::Details<extensions::UnloadedExtensionInfo>( 702 details)->extension; 703 if (extension_ == unloaded_extension) 704 native_app_window_->Close(); 705 break; 706 } 707 case chrome::NOTIFICATION_APP_TERMINATING: 708 native_app_window_->Close(); 709 break; 710 default: 711 NOTREACHED() << "Received unexpected notification"; 712 } 713 } 714 715 void ShellWindow::SetWebContentsBlocked(content::WebContents* web_contents, 716 bool blocked) { 717 delegate_->SetWebContentsBlocked(web_contents, blocked); 718 } 719 720 bool ShellWindow::IsWebContentsVisible(content::WebContents* web_contents) { 721 return delegate_->IsWebContentsVisible(web_contents); 722 } 723 724 extensions::ActiveTabPermissionGranter* 725 ShellWindow::GetActiveTabPermissionGranter() { 726 // Shell windows don't support the activeTab permission. 727 return NULL; 728 } 729 730 WebContentsModalDialogHost* ShellWindow::GetWebContentsModalDialogHost() { 731 return native_app_window_.get(); 732 } 733 734 void ShellWindow::AddMessageToDevToolsConsole(ConsoleMessageLevel level, 735 const std::string& message) { 736 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); 737 rvh->Send(new ExtensionMsg_AddMessageToConsole( 738 rvh->GetRoutingID(), level, message)); 739 } 740 741 void ShellWindow::SaveWindowPosition() { 742 if (window_key_.empty()) 743 return; 744 if (!native_app_window_) 745 return; 746 747 ShellWindowGeometryCache* cache = ShellWindowGeometryCache::Get(profile()); 748 749 gfx::Rect bounds = native_app_window_->GetRestoredBounds(); 750 bounds.Inset(native_app_window_->GetFrameInsets()); 751 gfx::Rect screen_bounds = 752 gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds).work_area(); 753 ui::WindowShowState window_state = native_app_window_->GetRestoredState(); 754 cache->SaveGeometry(extension()->id(), 755 window_key_, 756 bounds, 757 screen_bounds, 758 window_state); 759 } 760 761 void ShellWindow::AdjustBoundsToBeVisibleOnScreen( 762 const gfx::Rect& cached_bounds, 763 const gfx::Rect& cached_screen_bounds, 764 const gfx::Rect& current_screen_bounds, 765 const gfx::Size& minimum_size, 766 gfx::Rect* bounds) const { 767 *bounds = cached_bounds; 768 769 // Reposition and resize the bounds if the cached_screen_bounds is different 770 // from the current screen bounds and the current screen bounds doesn't 771 // completely contain the bounds. 772 if (cached_screen_bounds != current_screen_bounds && 773 !current_screen_bounds.Contains(cached_bounds)) { 774 bounds->set_width( 775 std::max(minimum_size.width(), 776 std::min(bounds->width(), current_screen_bounds.width()))); 777 bounds->set_height( 778 std::max(minimum_size.height(), 779 std::min(bounds->height(), current_screen_bounds.height()))); 780 bounds->set_x( 781 std::max(current_screen_bounds.x(), 782 std::min(bounds->x(), 783 current_screen_bounds.right() - bounds->width()))); 784 bounds->set_y( 785 std::max(current_screen_bounds.y(), 786 std::min(bounds->y(), 787 current_screen_bounds.bottom() - bounds->height()))); 788 } 789 } 790 791 ShellWindow::CreateParams ShellWindow::LoadDefaultsAndConstrain( 792 CreateParams params) const { 793 if (params.bounds.width() == 0) 794 params.bounds.set_width(kDefaultWidth); 795 if (params.bounds.height() == 0) 796 params.bounds.set_height(kDefaultHeight); 797 798 // If left and top are left undefined, the native shell window will center 799 // the window on the main screen in a platform-defined manner. 800 801 // Load cached state if it exists. 802 if (!params.window_key.empty()) { 803 ShellWindowGeometryCache* cache = ShellWindowGeometryCache::Get(profile()); 804 805 gfx::Rect cached_bounds; 806 gfx::Rect cached_screen_bounds; 807 ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT; 808 if (cache->GetGeometry(extension()->id(), params.window_key, 809 &cached_bounds, &cached_screen_bounds, 810 &cached_state)) { 811 // App window has cached screen bounds, make sure it fits on screen in 812 // case the screen resolution changed. 813 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 814 gfx::Display display = screen->GetDisplayMatching(cached_bounds); 815 gfx::Rect current_screen_bounds = display.work_area(); 816 AdjustBoundsToBeVisibleOnScreen(cached_bounds, 817 cached_screen_bounds, 818 current_screen_bounds, 819 params.minimum_size, 820 ¶ms.bounds); 821 params.state = cached_state; 822 } 823 } 824 825 SizeConstraints size_constraints(params.minimum_size, params.maximum_size); 826 params.bounds.set_size(size_constraints.ClampSize(params.bounds.size())); 827 params.minimum_size = size_constraints.GetMinimumSize(); 828 params.maximum_size = size_constraints.GetMaximumSize(); 829 830 return params; 831 } 832 833 // static 834 SkRegion* ShellWindow::RawDraggableRegionsToSkRegion( 835 const std::vector<extensions::DraggableRegion>& regions) { 836 SkRegion* sk_region = new SkRegion; 837 for (std::vector<extensions::DraggableRegion>::const_iterator iter = 838 regions.begin(); 839 iter != regions.end(); ++iter) { 840 const extensions::DraggableRegion& region = *iter; 841 sk_region->op( 842 region.bounds.x(), 843 region.bounds.y(), 844 region.bounds.right(), 845 region.bounds.bottom(), 846 region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); 847 } 848 return sk_region; 849 } 850 851 } // namespace apps 852