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