1// Copyright 2014 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/app_window.h" 6 7#include <algorithm> 8 9#include "apps/app_window_geometry_cache.h" 10#include "apps/app_window_registry.h" 11#include "apps/apps_client.h" 12#include "apps/size_constraints.h" 13#include "apps/ui/native_app_window.h" 14#include "apps/ui/web_contents_sizer.h" 15#include "base/command_line.h" 16#include "base/strings/string_util.h" 17#include "base/strings/utf_string_conversions.h" 18#include "base/values.h" 19#include "chrome/browser/chrome_notification_types.h" 20#include "chrome/browser/extensions/chrome_extension_web_contents_observer.h" 21#include "chrome/browser/extensions/suggest_permission_util.h" 22#include "chrome/common/chrome_switches.h" 23#include "components/web_modal/web_contents_modal_dialog_manager.h" 24#include "content/public/browser/browser_context.h" 25#include "content/public/browser/invalidate_type.h" 26#include "content/public/browser/navigation_entry.h" 27#include "content/public/browser/notification_details.h" 28#include "content/public/browser/notification_service.h" 29#include "content/public/browser/notification_source.h" 30#include "content/public/browser/notification_types.h" 31#include "content/public/browser/render_view_host.h" 32#include "content/public/browser/resource_dispatcher_host.h" 33#include "content/public/browser/web_contents.h" 34#include "content/public/common/content_switches.h" 35#include "content/public/common/media_stream_request.h" 36#include "extensions/browser/extension_registry.h" 37#include "extensions/browser/extension_system.h" 38#include "extensions/browser/extensions_browser_client.h" 39#include "extensions/browser/process_manager.h" 40#include "extensions/browser/view_type_utils.h" 41#include "extensions/common/extension.h" 42#include "extensions/common/extension_messages.h" 43#include "extensions/common/manifest_handlers/icons_handler.h" 44#include "extensions/common/permissions/permissions_data.h" 45#include "grit/theme_resources.h" 46#include "third_party/skia/include/core/SkRegion.h" 47#include "ui/base/resource/resource_bundle.h" 48#include "ui/gfx/screen.h" 49 50#if !defined(OS_MACOSX) 51#include "apps/pref_names.h" 52#include "base/prefs/pref_service.h" 53#endif 54 55using content::BrowserContext; 56using content::ConsoleMessageLevel; 57using content::WebContents; 58using extensions::APIPermission; 59using web_modal::WebContentsModalDialogHost; 60using web_modal::WebContentsModalDialogManager; 61 62namespace apps { 63 64namespace { 65 66const int kDefaultWidth = 512; 67const int kDefaultHeight = 384; 68 69bool IsFullscreen(int fullscreen_types) { 70 return fullscreen_types != apps::AppWindow::FULLSCREEN_TYPE_NONE; 71} 72 73void SetConstraintProperty(const std::string& name, 74 int value, 75 base::DictionaryValue* bounds_properties) { 76 if (value != SizeConstraints::kUnboundedSize) 77 bounds_properties->SetInteger(name, value); 78 else 79 bounds_properties->Set(name, base::Value::CreateNullValue()); 80} 81 82void SetBoundsProperties(const gfx::Rect& bounds, 83 const gfx::Size& min_size, 84 const gfx::Size& max_size, 85 const std::string& bounds_name, 86 base::DictionaryValue* window_properties) { 87 scoped_ptr<base::DictionaryValue> bounds_properties( 88 new base::DictionaryValue()); 89 90 bounds_properties->SetInteger("left", bounds.x()); 91 bounds_properties->SetInteger("top", bounds.y()); 92 bounds_properties->SetInteger("width", bounds.width()); 93 bounds_properties->SetInteger("height", bounds.height()); 94 95 SetConstraintProperty("minWidth", min_size.width(), bounds_properties.get()); 96 SetConstraintProperty( 97 "minHeight", min_size.height(), bounds_properties.get()); 98 SetConstraintProperty("maxWidth", max_size.width(), bounds_properties.get()); 99 SetConstraintProperty( 100 "maxHeight", max_size.height(), bounds_properties.get()); 101 102 window_properties->Set(bounds_name, bounds_properties.release()); 103} 104 105// Combines the constraints of the content and window, and returns constraints 106// for the window. 107gfx::Size GetCombinedWindowConstraints(const gfx::Size& window_constraints, 108 const gfx::Size& content_constraints, 109 const gfx::Insets& frame_insets) { 110 gfx::Size combined_constraints(window_constraints); 111 if (content_constraints.width() > 0) { 112 combined_constraints.set_width( 113 content_constraints.width() + frame_insets.width()); 114 } 115 if (content_constraints.height() > 0) { 116 combined_constraints.set_height( 117 content_constraints.height() + frame_insets.height()); 118 } 119 return combined_constraints; 120} 121 122// Combines the constraints of the content and window, and returns constraints 123// for the content. 124gfx::Size GetCombinedContentConstraints(const gfx::Size& window_constraints, 125 const gfx::Size& content_constraints, 126 const gfx::Insets& frame_insets) { 127 gfx::Size combined_constraints(content_constraints); 128 if (window_constraints.width() > 0) { 129 combined_constraints.set_width( 130 std::max(0, window_constraints.width() - frame_insets.width())); 131 } 132 if (window_constraints.height() > 0) { 133 combined_constraints.set_height( 134 std::max(0, window_constraints.height() - frame_insets.height())); 135 } 136 return combined_constraints; 137} 138 139} // namespace 140 141// AppWindow::BoundsSpecification 142 143const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN; 144 145AppWindow::BoundsSpecification::BoundsSpecification() 146 : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {} 147 148AppWindow::BoundsSpecification::~BoundsSpecification() {} 149 150void AppWindow::BoundsSpecification::ResetBounds() { 151 bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0); 152} 153 154// AppWindow::CreateParams 155 156AppWindow::CreateParams::CreateParams() 157 : window_type(AppWindow::WINDOW_TYPE_DEFAULT), 158 frame(AppWindow::FRAME_CHROME), 159 has_frame_color(false), 160 active_frame_color(SK_ColorBLACK), 161 inactive_frame_color(SK_ColorBLACK), 162 transparent_background(false), 163 creator_process_id(0), 164 state(ui::SHOW_STATE_DEFAULT), 165 hidden(false), 166 resizable(true), 167 focused(true), 168 always_on_top(false) {} 169 170AppWindow::CreateParams::~CreateParams() {} 171 172gfx::Rect AppWindow::CreateParams::GetInitialWindowBounds( 173 const gfx::Insets& frame_insets) const { 174 // Combine into a single window bounds. 175 gfx::Rect combined_bounds(window_spec.bounds); 176 if (content_spec.bounds.x() != BoundsSpecification::kUnspecifiedPosition) 177 combined_bounds.set_x(content_spec.bounds.x() - frame_insets.left()); 178 if (content_spec.bounds.y() != BoundsSpecification::kUnspecifiedPosition) 179 combined_bounds.set_y(content_spec.bounds.y() - frame_insets.top()); 180 if (content_spec.bounds.width() > 0) { 181 combined_bounds.set_width( 182 content_spec.bounds.width() + frame_insets.width()); 183 } 184 if (content_spec.bounds.height() > 0) { 185 combined_bounds.set_height( 186 content_spec.bounds.height() + frame_insets.height()); 187 } 188 189 // Constrain the bounds. 190 SizeConstraints constraints( 191 GetCombinedWindowConstraints( 192 window_spec.minimum_size, content_spec.minimum_size, frame_insets), 193 GetCombinedWindowConstraints( 194 window_spec.maximum_size, content_spec.maximum_size, frame_insets)); 195 combined_bounds.set_size(constraints.ClampSize(combined_bounds.size())); 196 197 return combined_bounds; 198} 199 200gfx::Size AppWindow::CreateParams::GetContentMinimumSize( 201 const gfx::Insets& frame_insets) const { 202 return GetCombinedContentConstraints(window_spec.minimum_size, 203 content_spec.minimum_size, 204 frame_insets); 205} 206 207gfx::Size AppWindow::CreateParams::GetContentMaximumSize( 208 const gfx::Insets& frame_insets) const { 209 return GetCombinedContentConstraints(window_spec.maximum_size, 210 content_spec.maximum_size, 211 frame_insets); 212} 213 214gfx::Size AppWindow::CreateParams::GetWindowMinimumSize( 215 const gfx::Insets& frame_insets) const { 216 return GetCombinedWindowConstraints(window_spec.minimum_size, 217 content_spec.minimum_size, 218 frame_insets); 219} 220 221gfx::Size AppWindow::CreateParams::GetWindowMaximumSize( 222 const gfx::Insets& frame_insets) const { 223 return GetCombinedWindowConstraints(window_spec.maximum_size, 224 content_spec.maximum_size, 225 frame_insets); 226} 227 228// AppWindow::Delegate 229 230AppWindow::Delegate::~Delegate() {} 231 232// AppWindow 233 234AppWindow::AppWindow(BrowserContext* context, 235 Delegate* delegate, 236 const extensions::Extension* extension) 237 : browser_context_(context), 238 extension_id_(extension->id()), 239 window_type_(WINDOW_TYPE_DEFAULT), 240 delegate_(delegate), 241 image_loader_ptr_factory_(this), 242 fullscreen_types_(FULLSCREEN_TYPE_NONE), 243 show_on_first_paint_(false), 244 first_paint_complete_(false), 245 has_been_shown_(false), 246 can_send_events_(false), 247 is_hidden_(false), 248 cached_always_on_top_(false), 249 requested_transparent_background_(false) { 250 extensions::ExtensionsBrowserClient* client = 251 extensions::ExtensionsBrowserClient::Get(); 252 CHECK(!client->IsGuestSession(context) || context->IsOffTheRecord()) 253 << "Only off the record window may be opened in the guest mode."; 254} 255 256void AppWindow::Init(const GURL& url, 257 AppWindowContents* app_window_contents, 258 const CreateParams& params) { 259 // Initialize the render interface and web contents 260 app_window_contents_.reset(app_window_contents); 261 app_window_contents_->Initialize(browser_context(), url); 262 WebContents* web_contents = app_window_contents_->GetWebContents(); 263 if (CommandLine::ForCurrentProcess()->HasSwitch( 264 switches::kEnableAppsShowOnFirstPaint)) { 265 content::WebContentsObserver::Observe(web_contents); 266 } 267 delegate_->InitWebContents(web_contents); 268 WebContentsModalDialogManager::CreateForWebContents(web_contents); 269 // TODO(jamescook): Delegate out this creation. 270 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents( 271 web_contents); 272 273 web_contents->SetDelegate(this); 274 WebContentsModalDialogManager::FromWebContents(web_contents) 275 ->SetDelegate(this); 276 extensions::SetViewType(web_contents, extensions::VIEW_TYPE_APP_WINDOW); 277 278 // Initialize the window 279 CreateParams new_params = LoadDefaults(params); 280 window_type_ = new_params.window_type; 281 window_key_ = new_params.window_key; 282 283 // Windows cannot be always-on-top in fullscreen mode for security reasons. 284 cached_always_on_top_ = new_params.always_on_top; 285 if (new_params.state == ui::SHOW_STATE_FULLSCREEN) 286 new_params.always_on_top = false; 287 288 requested_transparent_background_ = new_params.transparent_background; 289 290 native_app_window_.reset(delegate_->CreateNativeAppWindow(this, new_params)); 291 292 // Prevent the browser process from shutting down while this window exists. 293 AppsClient::Get()->IncrementKeepAliveCount(); 294 UpdateExtensionAppIcon(); 295 AppWindowRegistry::Get(browser_context_)->AddAppWindow(this); 296 297 if (new_params.hidden) { 298 // Although the window starts hidden by default, calling Hide() here 299 // notifies observers of the window being hidden. 300 Hide(); 301 } else { 302 // Panels are not activated by default. 303 Show(window_type_is_panel() || !new_params.focused ? SHOW_INACTIVE 304 : SHOW_ACTIVE); 305 } 306 307 if (new_params.state == ui::SHOW_STATE_FULLSCREEN) 308 Fullscreen(); 309 else if (new_params.state == ui::SHOW_STATE_MAXIMIZED) 310 Maximize(); 311 else if (new_params.state == ui::SHOW_STATE_MINIMIZED) 312 Minimize(); 313 314 OnNativeWindowChanged(); 315 316 // When the render view host is changed, the native window needs to know 317 // about it in case it has any setup to do to make the renderer appear 318 // properly. In particular, on Windows, the view's clickthrough region needs 319 // to be set. 320 extensions::ExtensionsBrowserClient* client = 321 extensions::ExtensionsBrowserClient::Get(); 322 registrar_.Add(this, 323 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, 324 content::Source<content::BrowserContext>( 325 client->GetOriginalContext(browser_context_))); 326 // Close when the browser process is exiting. 327 registrar_.Add(this, 328 chrome::NOTIFICATION_APP_TERMINATING, 329 content::NotificationService::AllSources()); 330 // Update the app menu if an ephemeral app becomes installed. 331 registrar_.Add(this, 332 chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED, 333 content::Source<content::BrowserContext>( 334 client->GetOriginalContext(browser_context_))); 335 336 app_window_contents_->LoadContents(new_params.creator_process_id); 337 338 if (CommandLine::ForCurrentProcess()->HasSwitch( 339 switches::kEnableAppsShowOnFirstPaint)) { 340 // We want to show the window only when the content has been painted. For 341 // that to happen, we need to define a size for the content, otherwise the 342 // layout will happen in a 0x0 area. 343 gfx::Insets frame_insets = native_app_window_->GetFrameInsets(); 344 gfx::Rect initial_bounds = new_params.GetInitialWindowBounds(frame_insets); 345 initial_bounds.Inset(frame_insets); 346 apps::ResizeWebContents(web_contents, initial_bounds.size()); 347 } 348} 349 350AppWindow::~AppWindow() { 351 // Unregister now to prevent getting NOTIFICATION_APP_TERMINATING if we're the 352 // last window open. 353 registrar_.RemoveAll(); 354 355 // Remove shutdown prevention. 356 AppsClient::Get()->DecrementKeepAliveCount(); 357} 358 359void AppWindow::RequestMediaAccessPermission( 360 content::WebContents* web_contents, 361 const content::MediaStreamRequest& request, 362 const content::MediaResponseCallback& callback) { 363 const extensions::Extension* extension = GetExtension(); 364 if (!extension) 365 return; 366 367 delegate_->RequestMediaAccessPermission( 368 web_contents, request, callback, extension); 369} 370 371WebContents* AppWindow::OpenURLFromTab(WebContents* source, 372 const content::OpenURLParams& params) { 373 // Don't allow the current tab to be navigated. It would be nice to map all 374 // anchor tags (even those without target="_blank") to new tabs, but right 375 // now we can't distinguish between those and <meta> refreshes or window.href 376 // navigations, which we don't want to allow. 377 // TOOD(mihaip): Can we check for user gestures instead? 378 WindowOpenDisposition disposition = params.disposition; 379 if (disposition == CURRENT_TAB) { 380 AddMessageToDevToolsConsole( 381 content::CONSOLE_MESSAGE_LEVEL_ERROR, 382 base::StringPrintf( 383 "Can't open same-window link to \"%s\"; try target=\"_blank\".", 384 params.url.spec().c_str())); 385 return NULL; 386 } 387 388 // These dispositions aren't really navigations. 389 if (disposition == SUPPRESS_OPEN || disposition == SAVE_TO_DISK || 390 disposition == IGNORE_ACTION) { 391 return NULL; 392 } 393 394 WebContents* contents = 395 delegate_->OpenURLFromTab(browser_context_, source, params); 396 if (!contents) { 397 AddMessageToDevToolsConsole( 398 content::CONSOLE_MESSAGE_LEVEL_ERROR, 399 base::StringPrintf( 400 "Can't navigate to \"%s\"; apps do not support navigation.", 401 params.url.spec().c_str())); 402 } 403 404 return contents; 405} 406 407void AppWindow::AddNewContents(WebContents* source, 408 WebContents* new_contents, 409 WindowOpenDisposition disposition, 410 const gfx::Rect& initial_pos, 411 bool user_gesture, 412 bool* was_blocked) { 413 DCHECK(new_contents->GetBrowserContext() == browser_context_); 414 delegate_->AddNewContents(browser_context_, 415 new_contents, 416 disposition, 417 initial_pos, 418 user_gesture, 419 was_blocked); 420} 421 422bool AppWindow::PreHandleKeyboardEvent( 423 content::WebContents* source, 424 const content::NativeWebKeyboardEvent& event, 425 bool* is_keyboard_shortcut) { 426 const extensions::Extension* extension = GetExtension(); 427 if (!extension) 428 return false; 429 430 // Here, we can handle a key event before the content gets it. When we are 431 // fullscreen and it is not forced, we want to allow the user to leave 432 // when ESC is pressed. 433 // However, if the application has the "overrideEscFullscreen" permission, we 434 // should let it override that behavior. 435 // ::HandleKeyboardEvent() will only be called if the KeyEvent's default 436 // action is not prevented. 437 // Thus, we should handle the KeyEvent here only if the permission is not set. 438 if (event.windowsKeyCode == ui::VKEY_ESCAPE && 439 (fullscreen_types_ != FULLSCREEN_TYPE_NONE) && 440 ((fullscreen_types_ & FULLSCREEN_TYPE_FORCED) == 0) && 441 !extension->permissions_data()->HasAPIPermission( 442 APIPermission::kOverrideEscFullscreen)) { 443 Restore(); 444 return true; 445 } 446 447 return false; 448} 449 450void AppWindow::HandleKeyboardEvent( 451 WebContents* source, 452 const content::NativeWebKeyboardEvent& event) { 453 // If the window is currently fullscreen and not forced, ESC should leave 454 // fullscreen. If this code is being called for ESC, that means that the 455 // KeyEvent's default behavior was not prevented by the content. 456 if (event.windowsKeyCode == ui::VKEY_ESCAPE && 457 (fullscreen_types_ != FULLSCREEN_TYPE_NONE) && 458 ((fullscreen_types_ & FULLSCREEN_TYPE_FORCED) == 0)) { 459 Restore(); 460 return; 461 } 462 463 native_app_window_->HandleKeyboardEvent(event); 464} 465 466void AppWindow::RequestToLockMouse(WebContents* web_contents, 467 bool user_gesture, 468 bool last_unlocked_by_target) { 469 const extensions::Extension* extension = GetExtension(); 470 if (!extension) 471 return; 472 473 bool has_permission = IsExtensionWithPermissionOrSuggestInConsole( 474 APIPermission::kPointerLock, 475 extension, 476 web_contents->GetRenderViewHost()); 477 478 web_contents->GotResponseToLockMouseRequest(has_permission); 479} 480 481bool AppWindow::PreHandleGestureEvent(WebContents* source, 482 const blink::WebGestureEvent& event) { 483 // Disable pinch zooming in app windows. 484 return event.type == blink::WebGestureEvent::GesturePinchBegin || 485 event.type == blink::WebGestureEvent::GesturePinchUpdate || 486 event.type == blink::WebGestureEvent::GesturePinchEnd; 487} 488 489void AppWindow::DidFirstVisuallyNonEmptyPaint() { 490 first_paint_complete_ = true; 491 if (show_on_first_paint_) { 492 DCHECK(delayed_show_type_ == SHOW_ACTIVE || 493 delayed_show_type_ == SHOW_INACTIVE); 494 Show(delayed_show_type_); 495 } 496} 497 498void AppWindow::OnNativeClose() { 499 AppWindowRegistry::Get(browser_context_)->RemoveAppWindow(this); 500 if (app_window_contents_) { 501 WebContents* web_contents = app_window_contents_->GetWebContents(); 502 WebContentsModalDialogManager::FromWebContents(web_contents) 503 ->SetDelegate(NULL); 504 app_window_contents_->NativeWindowClosed(); 505 } 506 delete this; 507} 508 509void AppWindow::OnNativeWindowChanged() { 510 SaveWindowPosition(); 511 512#if defined(OS_WIN) 513 if (native_app_window_ && cached_always_on_top_ && 514 !IsFullscreen(fullscreen_types_) && !native_app_window_->IsMaximized() && 515 !native_app_window_->IsMinimized()) { 516 UpdateNativeAlwaysOnTop(); 517 } 518#endif 519 520 if (app_window_contents_ && native_app_window_) 521 app_window_contents_->NativeWindowChanged(native_app_window_.get()); 522} 523 524void AppWindow::OnNativeWindowActivated() { 525 AppWindowRegistry::Get(browser_context_)->AppWindowActivated(this); 526} 527 528content::WebContents* AppWindow::web_contents() const { 529 return app_window_contents_->GetWebContents(); 530} 531 532const extensions::Extension* AppWindow::GetExtension() const { 533 return extensions::ExtensionRegistry::Get(browser_context_) 534 ->enabled_extensions() 535 .GetByID(extension_id_); 536} 537 538NativeAppWindow* AppWindow::GetBaseWindow() { return native_app_window_.get(); } 539 540gfx::NativeWindow AppWindow::GetNativeWindow() { 541 return GetBaseWindow()->GetNativeWindow(); 542} 543 544gfx::Rect AppWindow::GetClientBounds() const { 545 gfx::Rect bounds = native_app_window_->GetBounds(); 546 bounds.Inset(native_app_window_->GetFrameInsets()); 547 return bounds; 548} 549 550base::string16 AppWindow::GetTitle() const { 551 const extensions::Extension* extension = GetExtension(); 552 if (!extension) 553 return base::string16(); 554 555 // WebContents::GetTitle() will return the page's URL if there's no <title> 556 // specified. However, we'd prefer to show the name of the extension in that 557 // case, so we directly inspect the NavigationEntry's title. 558 base::string16 title; 559 if (!web_contents() || !web_contents()->GetController().GetActiveEntry() || 560 web_contents()->GetController().GetActiveEntry()->GetTitle().empty()) { 561 title = base::UTF8ToUTF16(extension->name()); 562 } else { 563 title = web_contents()->GetTitle(); 564 } 565 base::RemoveChars(title, base::ASCIIToUTF16("\n"), &title); 566 return title; 567} 568 569void AppWindow::SetAppIconUrl(const GURL& url) { 570 // If the same url is being used for the badge, ignore it. 571 if (url == badge_icon_url_) 572 return; 573 574 // Avoid using any previous icons that were being downloaded. 575 image_loader_ptr_factory_.InvalidateWeakPtrs(); 576 577 // Reset |app_icon_image_| to abort pending image load (if any). 578 app_icon_image_.reset(); 579 580 app_icon_url_ = url; 581 web_contents()->DownloadImage( 582 url, 583 true, // is a favicon 584 0, // no maximum size 585 base::Bind(&AppWindow::DidDownloadFavicon, 586 image_loader_ptr_factory_.GetWeakPtr())); 587} 588 589void AppWindow::SetBadgeIconUrl(const GURL& url) { 590 // Avoid using any previous icons that were being downloaded. 591 image_loader_ptr_factory_.InvalidateWeakPtrs(); 592 593 // Reset |app_icon_image_| to abort pending image load (if any). 594 badge_icon_image_.reset(); 595 596 badge_icon_url_ = url; 597 web_contents()->DownloadImage( 598 url, 599 true, // is a favicon 600 0, // no maximum size 601 base::Bind(&AppWindow::DidDownloadFavicon, 602 image_loader_ptr_factory_.GetWeakPtr())); 603} 604 605void AppWindow::ClearBadge() { 606 badge_icon_image_.reset(); 607 badge_icon_url_ = GURL(); 608 UpdateBadgeIcon(gfx::Image()); 609} 610 611void AppWindow::UpdateShape(scoped_ptr<SkRegion> region) { 612 native_app_window_->UpdateShape(region.Pass()); 613} 614 615void AppWindow::UpdateDraggableRegions( 616 const std::vector<extensions::DraggableRegion>& regions) { 617 native_app_window_->UpdateDraggableRegions(regions); 618} 619 620void AppWindow::UpdateAppIcon(const gfx::Image& image) { 621 if (image.IsEmpty()) 622 return; 623 app_icon_ = image; 624 native_app_window_->UpdateWindowIcon(); 625 AppWindowRegistry::Get(browser_context_)->AppWindowIconChanged(this); 626} 627 628void AppWindow::Fullscreen() { 629#if !defined(OS_MACOSX) 630 // Do not enter fullscreen mode if disallowed by pref. 631 PrefService* prefs = 632 extensions::ExtensionsBrowserClient::Get()->GetPrefServiceForContext( 633 browser_context()); 634 if (!prefs->GetBoolean(prefs::kAppFullscreenAllowed)) 635 return; 636#endif 637 fullscreen_types_ |= FULLSCREEN_TYPE_WINDOW_API; 638 SetNativeWindowFullscreen(); 639} 640 641void AppWindow::Maximize() { GetBaseWindow()->Maximize(); } 642 643void AppWindow::Minimize() { GetBaseWindow()->Minimize(); } 644 645void AppWindow::Restore() { 646 if (IsFullscreen(fullscreen_types_)) { 647 fullscreen_types_ = FULLSCREEN_TYPE_NONE; 648 SetNativeWindowFullscreen(); 649 } else { 650 GetBaseWindow()->Restore(); 651 } 652} 653 654void AppWindow::OSFullscreen() { 655#if !defined(OS_MACOSX) 656 // Do not enter fullscreen mode if disallowed by pref. 657 PrefService* prefs = 658 extensions::ExtensionsBrowserClient::Get()->GetPrefServiceForContext( 659 browser_context()); 660 if (!prefs->GetBoolean(prefs::kAppFullscreenAllowed)) 661 return; 662#endif 663 fullscreen_types_ |= FULLSCREEN_TYPE_OS; 664 SetNativeWindowFullscreen(); 665} 666 667void AppWindow::ForcedFullscreen() { 668 fullscreen_types_ |= FULLSCREEN_TYPE_FORCED; 669 SetNativeWindowFullscreen(); 670} 671 672void AppWindow::SetContentSizeConstraints(const gfx::Size& min_size, 673 const gfx::Size& max_size) { 674 SizeConstraints constraints(min_size, max_size); 675 native_app_window_->SetContentSizeConstraints(constraints.GetMinimumSize(), 676 constraints.GetMaximumSize()); 677 678 gfx::Rect bounds = GetClientBounds(); 679 gfx::Size constrained_size = constraints.ClampSize(bounds.size()); 680 if (bounds.size() != constrained_size) { 681 bounds.set_size(constrained_size); 682 bounds.Inset(-native_app_window_->GetFrameInsets()); 683 native_app_window_->SetBounds(bounds); 684 } 685 OnNativeWindowChanged(); 686} 687 688void AppWindow::Show(ShowType show_type) { 689 is_hidden_ = false; 690 691 if (CommandLine::ForCurrentProcess()->HasSwitch( 692 switches::kEnableAppsShowOnFirstPaint)) { 693 show_on_first_paint_ = true; 694 695 if (!first_paint_complete_) { 696 delayed_show_type_ = show_type; 697 return; 698 } 699 } 700 701 switch (show_type) { 702 case SHOW_ACTIVE: 703 GetBaseWindow()->Show(); 704 break; 705 case SHOW_INACTIVE: 706 GetBaseWindow()->ShowInactive(); 707 break; 708 } 709 AppWindowRegistry::Get(browser_context_)->AppWindowShown(this); 710 711 has_been_shown_ = true; 712 SendOnWindowShownIfShown(); 713} 714 715void AppWindow::Hide() { 716 // This is there to prevent race conditions with Hide() being called before 717 // there was a non-empty paint. It should have no effect in a non-racy 718 // scenario where the application is hiding then showing a window: the second 719 // show will not be delayed. 720 is_hidden_ = true; 721 show_on_first_paint_ = false; 722 GetBaseWindow()->Hide(); 723 AppWindowRegistry::Get(browser_context_)->AppWindowHidden(this); 724} 725 726void AppWindow::SetAlwaysOnTop(bool always_on_top) { 727 if (cached_always_on_top_ == always_on_top) 728 return; 729 730 cached_always_on_top_ = always_on_top; 731 732 // As a security measure, do not allow fullscreen windows or windows that 733 // overlap the taskbar to be on top. The property will be applied when the 734 // window exits fullscreen and moves away from the taskbar. 735 if (!IsFullscreen(fullscreen_types_) && !IntersectsWithTaskbar()) 736 native_app_window_->SetAlwaysOnTop(always_on_top); 737 738 OnNativeWindowChanged(); 739} 740 741bool AppWindow::IsAlwaysOnTop() const { return cached_always_on_top_; } 742 743void AppWindow::WindowEventsReady() { 744 can_send_events_ = true; 745 SendOnWindowShownIfShown(); 746} 747 748void AppWindow::GetSerializedState(base::DictionaryValue* properties) const { 749 DCHECK(properties); 750 751 properties->SetBoolean("fullscreen", 752 native_app_window_->IsFullscreenOrPending()); 753 properties->SetBoolean("minimized", native_app_window_->IsMinimized()); 754 properties->SetBoolean("maximized", native_app_window_->IsMaximized()); 755 properties->SetBoolean("alwaysOnTop", IsAlwaysOnTop()); 756 properties->SetBoolean("hasFrameColor", native_app_window_->HasFrameColor()); 757 properties->SetBoolean("alphaEnabled", 758 requested_transparent_background_ && 759 native_app_window_->CanHaveAlphaEnabled()); 760 761 // These properties are undocumented and are to enable testing. Alpha is 762 // removed to 763 // make the values easier to check. 764 SkColor transparent_white = ~SK_ColorBLACK; 765 properties->SetInteger( 766 "activeFrameColor", 767 native_app_window_->ActiveFrameColor() & transparent_white); 768 properties->SetInteger( 769 "inactiveFrameColor", 770 native_app_window_->InactiveFrameColor() & transparent_white); 771 772 gfx::Rect content_bounds = GetClientBounds(); 773 gfx::Size content_min_size = native_app_window_->GetContentMinimumSize(); 774 gfx::Size content_max_size = native_app_window_->GetContentMaximumSize(); 775 SetBoundsProperties(content_bounds, 776 content_min_size, 777 content_max_size, 778 "innerBounds", 779 properties); 780 781 gfx::Insets frame_insets = native_app_window_->GetFrameInsets(); 782 gfx::Rect frame_bounds = native_app_window_->GetBounds(); 783 gfx::Size frame_min_size = 784 SizeConstraints::AddFrameToConstraints(content_min_size, frame_insets); 785 gfx::Size frame_max_size = 786 SizeConstraints::AddFrameToConstraints(content_max_size, frame_insets); 787 SetBoundsProperties(frame_bounds, 788 frame_min_size, 789 frame_max_size, 790 "outerBounds", 791 properties); 792} 793 794//------------------------------------------------------------------------------ 795// Private methods 796 797void AppWindow::UpdateBadgeIcon(const gfx::Image& image) { 798 badge_icon_ = image; 799 native_app_window_->UpdateBadgeIcon(); 800} 801 802void AppWindow::DidDownloadFavicon( 803 int id, 804 int http_status_code, 805 const GURL& image_url, 806 const std::vector<SkBitmap>& bitmaps, 807 const std::vector<gfx::Size>& original_bitmap_sizes) { 808 if ((image_url != app_icon_url_ && image_url != badge_icon_url_) || 809 bitmaps.empty()) { 810 return; 811 } 812 813 // Bitmaps are ordered largest to smallest. Choose the smallest bitmap 814 // whose height >= the preferred size. 815 int largest_index = 0; 816 for (size_t i = 1; i < bitmaps.size(); ++i) { 817 if (bitmaps[i].height() < delegate_->PreferredIconSize()) 818 break; 819 largest_index = i; 820 } 821 const SkBitmap& largest = bitmaps[largest_index]; 822 if (image_url == app_icon_url_) { 823 UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest)); 824 return; 825 } 826 827 UpdateBadgeIcon(gfx::Image::CreateFrom1xBitmap(largest)); 828} 829 830void AppWindow::OnExtensionIconImageChanged(extensions::IconImage* image) { 831 DCHECK_EQ(app_icon_image_.get(), image); 832 833 UpdateAppIcon(gfx::Image(app_icon_image_->image_skia())); 834} 835 836void AppWindow::UpdateExtensionAppIcon() { 837 // Avoid using any previous app icons were being downloaded. 838 image_loader_ptr_factory_.InvalidateWeakPtrs(); 839 840 const gfx::ImageSkia& default_icon = 841 *ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 842 IDR_APP_DEFAULT_ICON); 843 844 const extensions::Extension* extension = GetExtension(); 845 if (!extension) 846 return; 847 848 app_icon_image_.reset( 849 new extensions::IconImage(browser_context(), 850 extension, 851 extensions::IconsInfo::GetIcons(extension), 852 delegate_->PreferredIconSize(), 853 default_icon, 854 this)); 855 856 // Triggers actual image loading with 1x resources. The 2x resource will 857 // be handled by IconImage class when requested. 858 app_icon_image_->image_skia().GetRepresentation(1.0f); 859} 860 861void AppWindow::SetNativeWindowFullscreen() { 862 native_app_window_->SetFullscreen(fullscreen_types_); 863 864 if (cached_always_on_top_) 865 UpdateNativeAlwaysOnTop(); 866} 867 868bool AppWindow::IntersectsWithTaskbar() const { 869#if defined(OS_WIN) 870 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 871 gfx::Rect window_bounds = native_app_window_->GetRestoredBounds(); 872 std::vector<gfx::Display> displays = screen->GetAllDisplays(); 873 874 for (std::vector<gfx::Display>::const_iterator it = displays.begin(); 875 it != displays.end(); 876 ++it) { 877 gfx::Rect taskbar_bounds = it->bounds(); 878 taskbar_bounds.Subtract(it->work_area()); 879 if (taskbar_bounds.IsEmpty()) 880 continue; 881 882 if (window_bounds.Intersects(taskbar_bounds)) 883 return true; 884 } 885#endif 886 887 return false; 888} 889 890void AppWindow::UpdateNativeAlwaysOnTop() { 891 DCHECK(cached_always_on_top_); 892 bool is_on_top = native_app_window_->IsAlwaysOnTop(); 893 bool fullscreen = IsFullscreen(fullscreen_types_); 894 bool intersects_taskbar = IntersectsWithTaskbar(); 895 896 if (is_on_top && (fullscreen || intersects_taskbar)) { 897 // When entering fullscreen or overlapping the taskbar, ensure windows are 898 // not always-on-top. 899 native_app_window_->SetAlwaysOnTop(false); 900 } else if (!is_on_top && !fullscreen && !intersects_taskbar) { 901 // When exiting fullscreen and moving away from the taskbar, reinstate 902 // always-on-top. 903 native_app_window_->SetAlwaysOnTop(true); 904 } 905} 906 907void AppWindow::SendOnWindowShownIfShown() { 908 if (!can_send_events_ || !has_been_shown_) 909 return; 910 911 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) { 912 app_window_contents_->DispatchWindowShownForTests(); 913 } 914} 915 916void AppWindow::CloseContents(WebContents* contents) { 917 native_app_window_->Close(); 918} 919 920bool AppWindow::ShouldSuppressDialogs() { return true; } 921 922content::ColorChooser* AppWindow::OpenColorChooser( 923 WebContents* web_contents, 924 SkColor initial_color, 925 const std::vector<content::ColorSuggestion>& suggestionss) { 926 return delegate_->ShowColorChooser(web_contents, initial_color); 927} 928 929void AppWindow::RunFileChooser(WebContents* tab, 930 const content::FileChooserParams& params) { 931 if (window_type_is_panel()) { 932 // Panels can't host a file dialog, abort. TODO(stevenjb): allow file 933 // dialogs to be unhosted but still close with the owning web contents. 934 // crbug.com/172502. 935 LOG(WARNING) << "File dialog opened by panel."; 936 return; 937 } 938 939 delegate_->RunFileChooser(tab, params); 940} 941 942bool AppWindow::IsPopupOrPanel(const WebContents* source) const { return true; } 943 944void AppWindow::MoveContents(WebContents* source, const gfx::Rect& pos) { 945 native_app_window_->SetBounds(pos); 946} 947 948void AppWindow::NavigationStateChanged(const content::WebContents* source, 949 unsigned changed_flags) { 950 if (changed_flags & content::INVALIDATE_TYPE_TITLE) 951 native_app_window_->UpdateWindowTitle(); 952 else if (changed_flags & content::INVALIDATE_TYPE_TAB) 953 native_app_window_->UpdateWindowIcon(); 954} 955 956void AppWindow::ToggleFullscreenModeForTab(content::WebContents* source, 957 bool enter_fullscreen) { 958#if !defined(OS_MACOSX) 959 // Do not enter fullscreen mode if disallowed by pref. 960 // TODO(bartfab): Add a test once it becomes possible to simulate a user 961 // gesture. http://crbug.com/174178 962 PrefService* prefs = 963 extensions::ExtensionsBrowserClient::Get()->GetPrefServiceForContext( 964 browser_context()); 965 if (enter_fullscreen && !prefs->GetBoolean(prefs::kAppFullscreenAllowed)) { 966 return; 967 } 968#endif 969 970 const extensions::Extension* extension = GetExtension(); 971 if (!extension) 972 return; 973 974 if (!IsExtensionWithPermissionOrSuggestInConsole( 975 APIPermission::kFullscreen, extension, source->GetRenderViewHost())) { 976 return; 977 } 978 979 if (enter_fullscreen) 980 fullscreen_types_ |= FULLSCREEN_TYPE_HTML_API; 981 else 982 fullscreen_types_ &= ~FULLSCREEN_TYPE_HTML_API; 983 SetNativeWindowFullscreen(); 984} 985 986bool AppWindow::IsFullscreenForTabOrPending(const content::WebContents* source) 987 const { 988 return ((fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0); 989} 990 991void AppWindow::Observe(int type, 992 const content::NotificationSource& source, 993 const content::NotificationDetails& details) { 994 switch (type) { 995 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { 996 const extensions::Extension* unloaded_extension = 997 content::Details<extensions::UnloadedExtensionInfo>(details) 998 ->extension; 999 if (extension_id_ == unloaded_extension->id()) 1000 native_app_window_->Close(); 1001 break; 1002 } 1003 case chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED: { 1004 const extensions::Extension* installed_extension = 1005 content::Details<const extensions::InstalledExtensionInfo>(details) 1006 ->extension; 1007 DCHECK(installed_extension); 1008 if (installed_extension->id() == extension_id()) 1009 native_app_window_->UpdateShelfMenu(); 1010 break; 1011 } 1012 case chrome::NOTIFICATION_APP_TERMINATING: 1013 native_app_window_->Close(); 1014 break; 1015 default: 1016 NOTREACHED() << "Received unexpected notification"; 1017 } 1018} 1019 1020void AppWindow::SetWebContentsBlocked(content::WebContents* web_contents, 1021 bool blocked) { 1022 delegate_->SetWebContentsBlocked(web_contents, blocked); 1023} 1024 1025bool AppWindow::IsWebContentsVisible(content::WebContents* web_contents) { 1026 return delegate_->IsWebContentsVisible(web_contents); 1027} 1028 1029WebContentsModalDialogHost* AppWindow::GetWebContentsModalDialogHost() { 1030 return native_app_window_.get(); 1031} 1032 1033void AppWindow::AddMessageToDevToolsConsole(ConsoleMessageLevel level, 1034 const std::string& message) { 1035 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); 1036 rvh->Send(new ExtensionMsg_AddMessageToConsole( 1037 rvh->GetRoutingID(), level, message)); 1038} 1039 1040void AppWindow::SaveWindowPosition() { 1041 if (window_key_.empty()) 1042 return; 1043 if (!native_app_window_) 1044 return; 1045 1046 AppWindowGeometryCache* cache = 1047 AppWindowGeometryCache::Get(browser_context()); 1048 1049 gfx::Rect bounds = native_app_window_->GetRestoredBounds(); 1050 gfx::Rect screen_bounds = 1051 gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds).work_area(); 1052 ui::WindowShowState window_state = native_app_window_->GetRestoredState(); 1053 cache->SaveGeometry( 1054 extension_id(), window_key_, bounds, screen_bounds, window_state); 1055} 1056 1057void AppWindow::AdjustBoundsToBeVisibleOnScreen( 1058 const gfx::Rect& cached_bounds, 1059 const gfx::Rect& cached_screen_bounds, 1060 const gfx::Rect& current_screen_bounds, 1061 const gfx::Size& minimum_size, 1062 gfx::Rect* bounds) const { 1063 *bounds = cached_bounds; 1064 1065 // Reposition and resize the bounds if the cached_screen_bounds is different 1066 // from the current screen bounds and the current screen bounds doesn't 1067 // completely contain the bounds. 1068 if (cached_screen_bounds != current_screen_bounds && 1069 !current_screen_bounds.Contains(cached_bounds)) { 1070 bounds->set_width( 1071 std::max(minimum_size.width(), 1072 std::min(bounds->width(), current_screen_bounds.width()))); 1073 bounds->set_height( 1074 std::max(minimum_size.height(), 1075 std::min(bounds->height(), current_screen_bounds.height()))); 1076 bounds->set_x( 1077 std::max(current_screen_bounds.x(), 1078 std::min(bounds->x(), 1079 current_screen_bounds.right() - bounds->width()))); 1080 bounds->set_y( 1081 std::max(current_screen_bounds.y(), 1082 std::min(bounds->y(), 1083 current_screen_bounds.bottom() - bounds->height()))); 1084 } 1085} 1086 1087AppWindow::CreateParams AppWindow::LoadDefaults(CreateParams params) 1088 const { 1089 // Ensure width and height are specified. 1090 if (params.content_spec.bounds.width() == 0 && 1091 params.window_spec.bounds.width() == 0) { 1092 params.content_spec.bounds.set_width(kDefaultWidth); 1093 } 1094 if (params.content_spec.bounds.height() == 0 && 1095 params.window_spec.bounds.height() == 0) { 1096 params.content_spec.bounds.set_height(kDefaultHeight); 1097 } 1098 1099 // If left and top are left undefined, the native app window will center 1100 // the window on the main screen in a platform-defined manner. 1101 1102 // Load cached state if it exists. 1103 if (!params.window_key.empty()) { 1104 AppWindowGeometryCache* cache = 1105 AppWindowGeometryCache::Get(browser_context()); 1106 1107 gfx::Rect cached_bounds; 1108 gfx::Rect cached_screen_bounds; 1109 ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT; 1110 if (cache->GetGeometry(extension_id(), 1111 params.window_key, 1112 &cached_bounds, 1113 &cached_screen_bounds, 1114 &cached_state)) { 1115 // App window has cached screen bounds, make sure it fits on screen in 1116 // case the screen resolution changed. 1117 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 1118 gfx::Display display = screen->GetDisplayMatching(cached_bounds); 1119 gfx::Rect current_screen_bounds = display.work_area(); 1120 SizeConstraints constraints(params.GetWindowMinimumSize(gfx::Insets()), 1121 params.GetWindowMaximumSize(gfx::Insets())); 1122 AdjustBoundsToBeVisibleOnScreen(cached_bounds, 1123 cached_screen_bounds, 1124 current_screen_bounds, 1125 constraints.GetMinimumSize(), 1126 ¶ms.window_spec.bounds); 1127 params.state = cached_state; 1128 1129 // Since we are restoring a cached state, reset the content bounds spec to 1130 // ensure it is not used. 1131 params.content_spec.ResetBounds(); 1132 } 1133 } 1134 1135 return params; 1136} 1137 1138// static 1139SkRegion* AppWindow::RawDraggableRegionsToSkRegion( 1140 const std::vector<extensions::DraggableRegion>& regions) { 1141 SkRegion* sk_region = new SkRegion; 1142 for (std::vector<extensions::DraggableRegion>::const_iterator iter = 1143 regions.begin(); 1144 iter != regions.end(); 1145 ++iter) { 1146 const extensions::DraggableRegion& region = *iter; 1147 sk_region->op( 1148 region.bounds.x(), 1149 region.bounds.y(), 1150 region.bounds.right(), 1151 region.bounds.bottom(), 1152 region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); 1153 } 1154 return sk_region; 1155} 1156 1157} // namespace apps 1158