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