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