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/native_app_window.h"
      8 #include "apps/shell_window_geometry_cache.h"
      9 #include "apps/shell_window_registry.h"
     10 #include "base/strings/string_util.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "base/values.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/extensions/extension_process_manager.h"
     15 #include "chrome/browser/extensions/extension_system.h"
     16 #include "chrome/browser/extensions/suggest_permission_util.h"
     17 #include "chrome/browser/lifetime/application_lifetime.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/common/extensions/extension.h"
     20 #include "chrome/common/extensions/extension_constants.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/common/media_stream_request.h"
     34 #include "extensions/browser/view_type_utils.h"
     35 #include "skia/ext/image_operations.h"
     36 #include "third_party/skia/include/core/SkRegion.h"
     37 #include "ui/gfx/image/image_skia.h"
     38 #include "ui/gfx/screen.h"
     39 
     40 using content::ConsoleMessageLevel;
     41 using content::WebContents;
     42 using extensions::APIPermission;
     43 using web_modal::WebContentsModalDialogHost;
     44 using web_modal::WebContentsModalDialogManager;
     45 
     46 namespace {
     47 const int kDefaultWidth = 512;
     48 const int kDefaultHeight = 384;
     49 
     50 }  // namespace
     51 
     52 namespace apps {
     53 
     54 ShellWindow::CreateParams::CreateParams()
     55   : window_type(ShellWindow::WINDOW_TYPE_DEFAULT),
     56     frame(ShellWindow::FRAME_CHROME),
     57     transparent_background(false),
     58     bounds(INT_MIN, INT_MIN, 0, 0),
     59     creator_process_id(0),
     60     state(ui::SHOW_STATE_DEFAULT),
     61     hidden(false),
     62     resizable(true),
     63     focused(true) {}
     64 
     65 ShellWindow::CreateParams::~CreateParams() {}
     66 
     67 ShellWindow::Delegate::~Delegate() {}
     68 
     69 ShellWindow::ShellWindow(Profile* profile,
     70                          Delegate* delegate,
     71                          const extensions::Extension* extension)
     72     : profile_(profile),
     73       extension_(extension),
     74       extension_id_(extension->id()),
     75       window_type_(WINDOW_TYPE_DEFAULT),
     76       delegate_(delegate),
     77       image_loader_ptr_factory_(this),
     78       fullscreen_for_window_api_(false),
     79       fullscreen_for_tab_(false) {
     80 }
     81 
     82 void ShellWindow::Init(const GURL& url,
     83                        ShellWindowContents* shell_window_contents,
     84                        const CreateParams& params) {
     85   // Initialize the render interface and web contents
     86   shell_window_contents_.reset(shell_window_contents);
     87   shell_window_contents_->Initialize(profile(), url);
     88   WebContents* web_contents = shell_window_contents_->GetWebContents();
     89   delegate_->InitWebContents(web_contents);
     90   WebContentsModalDialogManager::CreateForWebContents(web_contents);
     91 
     92   web_contents->SetDelegate(this);
     93   WebContentsModalDialogManager::FromWebContents(web_contents)->
     94       set_delegate(this);
     95   extensions::SetViewType(web_contents, extensions::VIEW_TYPE_APP_SHELL);
     96 
     97   // Initialize the window
     98   window_type_ = params.window_type;
     99 
    100   gfx::Rect bounds = params.bounds;
    101 
    102   if (bounds.width() == 0)
    103     bounds.set_width(kDefaultWidth);
    104   if (bounds.height() == 0)
    105     bounds.set_height(kDefaultHeight);
    106 
    107   // If left and top are left undefined, the native shell window will center
    108   // the window on the main screen in a platform-defined manner.
    109 
    110   CreateParams new_params = params;
    111 
    112   // Load cached state if it exists.
    113   if (!params.window_key.empty()) {
    114     window_key_ = params.window_key;
    115 
    116     ShellWindowGeometryCache* cache = ShellWindowGeometryCache::Get(profile());
    117 
    118     gfx::Rect cached_bounds;
    119     gfx::Rect cached_screen_bounds;
    120     ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT;
    121     if (cache->GetGeometry(extension()->id(), params.window_key, &cached_bounds,
    122                            &cached_screen_bounds, &cached_state)) {
    123       // App window has cached screen bounds, make sure it fits on screen in
    124       // case the screen resolution changed.
    125       gfx::Screen* screen = gfx::Screen::GetNativeScreen();
    126       gfx::Display display = screen->GetDisplayMatching(cached_bounds);
    127       gfx::Rect current_screen_bounds = display.work_area();
    128       AdjustBoundsToBeVisibleOnScreen(cached_bounds,
    129                                       cached_screen_bounds,
    130                                       current_screen_bounds,
    131                                       params.minimum_size,
    132                                       &bounds);
    133       new_params.state = cached_state;
    134     }
    135   }
    136 
    137   gfx::Size& minimum_size = new_params.minimum_size;
    138   gfx::Size& maximum_size = new_params.maximum_size;
    139 
    140   // In the case that minimum size > maximum size, we consider the minimum
    141   // size to be more important.
    142   if (maximum_size.width() && maximum_size.width() < minimum_size.width())
    143     maximum_size.set_width(minimum_size.width());
    144   if (maximum_size.height() && maximum_size.height() < minimum_size.height())
    145     maximum_size.set_height(minimum_size.height());
    146 
    147   if (maximum_size.width() && bounds.width() > maximum_size.width())
    148     bounds.set_width(maximum_size.width());
    149   if (bounds.width() != INT_MIN && bounds.width() < minimum_size.width())
    150     bounds.set_width(minimum_size.width());
    151 
    152   if (maximum_size.height() && bounds.height() > maximum_size.height())
    153     bounds.set_height(maximum_size.height());
    154   if (bounds.height() != INT_MIN && bounds.height() < minimum_size.height())
    155     bounds.set_height(minimum_size.height());
    156 
    157   new_params.bounds = bounds;
    158 
    159   native_app_window_.reset(delegate_->CreateNativeAppWindow(this, new_params));
    160 
    161   if (!new_params.hidden) {
    162     if (window_type_is_panel())
    163       GetBaseWindow()->ShowInactive();  // Panels are not activated by default.
    164     else
    165       GetBaseWindow()->Show();
    166   }
    167 
    168   if (new_params.state == ui::SHOW_STATE_FULLSCREEN)
    169     Fullscreen();
    170   else if (new_params.state == ui::SHOW_STATE_MAXIMIZED)
    171     Maximize();
    172   else if (new_params.state == ui::SHOW_STATE_MINIMIZED)
    173     Minimize();
    174 
    175   OnNativeWindowChanged();
    176 
    177   // When the render view host is changed, the native window needs to know
    178   // about it in case it has any setup to do to make the renderer appear
    179   // properly. In particular, on Windows, the view's clickthrough region needs
    180   // to be set.
    181   registrar_.Add(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
    182                  content::Source<content::NavigationController>(
    183                     &web_contents->GetController()));
    184   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
    185                  content::Source<Profile>(profile_));
    186   // Close when the browser process is exiting.
    187   registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
    188                  content::NotificationService::AllSources());
    189 
    190   shell_window_contents_->LoadContents(params.creator_process_id);
    191 
    192   // Prevent the browser process from shutting down while this window is open.
    193   chrome::StartKeepAlive();
    194 
    195   UpdateExtensionAppIcon();
    196 
    197   ShellWindowRegistry::Get(profile_)->AddShellWindow(this);
    198 }
    199 
    200 ShellWindow::~ShellWindow() {
    201   // Unregister now to prevent getting NOTIFICATION_APP_TERMINATING if we're the
    202   // last window open.
    203   registrar_.RemoveAll();
    204 
    205   // Remove shutdown prevention.
    206   chrome::EndKeepAlive();
    207 }
    208 
    209 void ShellWindow::RequestMediaAccessPermission(
    210     content::WebContents* web_contents,
    211     const content::MediaStreamRequest& request,
    212     const content::MediaResponseCallback& callback) {
    213   delegate_->RequestMediaAccessPermission(web_contents, request, callback,
    214                                           extension());
    215 }
    216 
    217 WebContents* ShellWindow::OpenURLFromTab(WebContents* source,
    218                                          const content::OpenURLParams& params) {
    219   // Don't allow the current tab to be navigated. It would be nice to map all
    220   // anchor tags (even those without target="_blank") to new tabs, but right
    221   // now we can't distinguish between those and <meta> refreshes or window.href
    222   // navigations, which we don't want to allow.
    223   // TOOD(mihaip): Can we check for user gestures instead?
    224   WindowOpenDisposition disposition = params.disposition;
    225   if (disposition == CURRENT_TAB) {
    226     AddMessageToDevToolsConsole(
    227         content::CONSOLE_MESSAGE_LEVEL_ERROR,
    228         base::StringPrintf(
    229             "Can't open same-window link to \"%s\"; try target=\"_blank\".",
    230             params.url.spec().c_str()));
    231     return NULL;
    232   }
    233 
    234   // These dispositions aren't really navigations.
    235   if (disposition == SUPPRESS_OPEN || disposition == SAVE_TO_DISK ||
    236       disposition == IGNORE_ACTION) {
    237     return NULL;
    238   }
    239 
    240   WebContents* contents = delegate_->OpenURLFromTab(profile_, source,
    241                                                     params);
    242   if (!contents) {
    243     AddMessageToDevToolsConsole(
    244         content::CONSOLE_MESSAGE_LEVEL_ERROR,
    245         base::StringPrintf(
    246             "Can't navigate to \"%s\"; apps do not support navigation.",
    247             params.url.spec().c_str()));
    248   }
    249 
    250   return contents;
    251 }
    252 
    253 void ShellWindow::AddNewContents(WebContents* source,
    254                                  WebContents* new_contents,
    255                                  WindowOpenDisposition disposition,
    256                                  const gfx::Rect& initial_pos,
    257                                  bool user_gesture,
    258                                  bool* was_blocked) {
    259   DCHECK(Profile::FromBrowserContext(new_contents->GetBrowserContext()) ==
    260       profile_);
    261   delegate_->AddNewContents(profile_, new_contents, disposition,
    262                             initial_pos, user_gesture, was_blocked);
    263 }
    264 
    265 void ShellWindow::HandleKeyboardEvent(
    266     WebContents* source,
    267     const content::NativeWebKeyboardEvent& event) {
    268   native_app_window_->HandleKeyboardEvent(event);
    269 }
    270 
    271 void ShellWindow::RequestToLockMouse(WebContents* web_contents,
    272                                      bool user_gesture,
    273                                      bool last_unlocked_by_target) {
    274   bool has_permission = IsExtensionWithPermissionOrSuggestInConsole(
    275       APIPermission::kPointerLock,
    276       extension_,
    277       web_contents->GetRenderViewHost());
    278 
    279   web_contents->GotResponseToLockMouseRequest(has_permission);
    280 }
    281 
    282 void ShellWindow::OnNativeClose() {
    283   ShellWindowRegistry::Get(profile_)->RemoveShellWindow(this);
    284   if (shell_window_contents_)
    285     shell_window_contents_->NativeWindowClosed();
    286   delete this;
    287 }
    288 
    289 void ShellWindow::OnNativeWindowChanged() {
    290   SaveWindowPosition();
    291   if (shell_window_contents_ && native_app_window_)
    292     shell_window_contents_->NativeWindowChanged(native_app_window_.get());
    293 }
    294 
    295 void ShellWindow::OnNativeWindowActivated() {
    296   ShellWindowRegistry::Get(profile_)->ShellWindowActivated(this);
    297 }
    298 
    299 scoped_ptr<gfx::Image> ShellWindow::GetAppListIcon() {
    300   // TODO(skuhne): We might want to use LoadImages in UpdateExtensionAppIcon
    301   // instead to let the extension give us pre-defined icons in the launcher
    302   // and the launcher list sizes. Since there is no mock yet, doing this now
    303   // seems a bit premature and we scale for the time being.
    304   if (app_icon_.IsEmpty())
    305     return make_scoped_ptr(new gfx::Image());
    306 
    307   SkBitmap bmp = skia::ImageOperations::Resize(
    308         *app_icon_.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
    309         extension_misc::EXTENSION_ICON_SMALLISH,
    310         extension_misc::EXTENSION_ICON_SMALLISH);
    311   return make_scoped_ptr(
    312       new gfx::Image(gfx::ImageSkia::CreateFrom1xBitmap(bmp)));
    313 }
    314 
    315 content::WebContents* ShellWindow::web_contents() const {
    316   return shell_window_contents_->GetWebContents();
    317 }
    318 
    319 NativeAppWindow* ShellWindow::GetBaseWindow() {
    320   return native_app_window_.get();
    321 }
    322 
    323 gfx::NativeWindow ShellWindow::GetNativeWindow() {
    324   return GetBaseWindow()->GetNativeWindow();
    325 }
    326 
    327 gfx::Rect ShellWindow::GetClientBounds() const {
    328   gfx::Rect bounds = native_app_window_->GetBounds();
    329   bounds.Inset(native_app_window_->GetFrameInsets());
    330   return bounds;
    331 }
    332 
    333 string16 ShellWindow::GetTitle() const {
    334   // WebContents::GetTitle() will return the page's URL if there's no <title>
    335   // specified. However, we'd prefer to show the name of the extension in that
    336   // case, so we directly inspect the NavigationEntry's title.
    337   string16 title;
    338   if (!web_contents() ||
    339       !web_contents()->GetController().GetActiveEntry() ||
    340       web_contents()->GetController().GetActiveEntry()->GetTitle().empty()) {
    341     title = UTF8ToUTF16(extension()->name());
    342   } else {
    343     title = web_contents()->GetTitle();
    344   }
    345   const char16 kBadChars[] = { '\n', 0 };
    346   RemoveChars(title, kBadChars, &title);
    347   return title;
    348 }
    349 
    350 void ShellWindow::SetAppIconUrl(const GURL& url) {
    351   // Avoid using any previous app icons were are being downloaded.
    352   image_loader_ptr_factory_.InvalidateWeakPtrs();
    353 
    354   // Reset |app_icon_image_| to abort pending image load (if any).
    355   app_icon_image_.reset();
    356 
    357   app_icon_url_ = url;
    358   web_contents()->DownloadImage(
    359       url,
    360       true,  // is a favicon
    361       delegate_->PreferredIconSize(),
    362       0,  // no maximum size
    363       base::Bind(&ShellWindow::DidDownloadFavicon,
    364                  image_loader_ptr_factory_.GetWeakPtr()));
    365 }
    366 
    367 void ShellWindow::UpdateDraggableRegions(
    368     const std::vector<extensions::DraggableRegion>& regions) {
    369   native_app_window_->UpdateDraggableRegions(regions);
    370 }
    371 
    372 void ShellWindow::UpdateAppIcon(const gfx::Image& image) {
    373   if (image.IsEmpty())
    374     return;
    375   app_icon_ = image;
    376   native_app_window_->UpdateWindowIcon();
    377   ShellWindowRegistry::Get(profile_)->ShellWindowIconChanged(this);
    378 }
    379 
    380 void ShellWindow::Fullscreen() {
    381   fullscreen_for_window_api_ = true;
    382   GetBaseWindow()->SetFullscreen(true);
    383 }
    384 
    385 void ShellWindow::Maximize() {
    386   GetBaseWindow()->Maximize();
    387 }
    388 
    389 void ShellWindow::Minimize() {
    390   GetBaseWindow()->Minimize();
    391 }
    392 
    393 void ShellWindow::Restore() {
    394   fullscreen_for_window_api_ = false;
    395   fullscreen_for_tab_ = false;
    396   if (GetBaseWindow()->IsFullscreenOrPending()) {
    397     GetBaseWindow()->SetFullscreen(false);
    398   } else {
    399     GetBaseWindow()->Restore();
    400   }
    401 }
    402 
    403 //------------------------------------------------------------------------------
    404 // Private methods
    405 
    406 void ShellWindow::DidDownloadFavicon(int id,
    407                                      int http_status_code,
    408                                      const GURL& image_url,
    409                                      int requested_size,
    410                                      const std::vector<SkBitmap>& bitmaps) {
    411   if (image_url != app_icon_url_ || bitmaps.empty())
    412     return;
    413 
    414   // Bitmaps are ordered largest to smallest. Choose the smallest bitmap
    415   // whose height >= the preferred size.
    416   int largest_index = 0;
    417   for (size_t i = 1; i < bitmaps.size(); ++i) {
    418     if (bitmaps[i].height() < delegate_->PreferredIconSize())
    419       break;
    420     largest_index = i;
    421   }
    422   const SkBitmap& largest = bitmaps[largest_index];
    423   UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest));
    424 }
    425 
    426 void ShellWindow::OnExtensionIconImageChanged(extensions::IconImage* image) {
    427   DCHECK_EQ(app_icon_image_.get(), image);
    428 
    429   UpdateAppIcon(gfx::Image(app_icon_image_->image_skia()));
    430 }
    431 
    432 void ShellWindow::UpdateExtensionAppIcon() {
    433   // Avoid using any previous app icons were are being downloaded.
    434   image_loader_ptr_factory_.InvalidateWeakPtrs();
    435 
    436   app_icon_image_.reset(new extensions::IconImage(
    437       profile(),
    438       extension(),
    439       extensions::IconsInfo::GetIcons(extension()),
    440       delegate_->PreferredIconSize(),
    441       extensions::IconsInfo::GetDefaultAppIcon(),
    442       this));
    443 
    444   // Triggers actual image loading with 1x resources. The 2x resource will
    445   // be handled by IconImage class when requested.
    446   app_icon_image_->image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
    447 }
    448 
    449 void ShellWindow::CloseContents(WebContents* contents) {
    450   native_app_window_->Close();
    451 }
    452 
    453 bool ShellWindow::ShouldSuppressDialogs() {
    454   return true;
    455 }
    456 
    457 content::ColorChooser* ShellWindow::OpenColorChooser(WebContents* web_contents,
    458                                                      SkColor initial_color) {
    459   return delegate_->ShowColorChooser(web_contents, initial_color);
    460 }
    461 
    462 void ShellWindow::RunFileChooser(WebContents* tab,
    463                                  const content::FileChooserParams& params) {
    464   if (window_type_is_panel()) {
    465     // Panels can't host a file dialog, abort. TODO(stevenjb): allow file
    466     // dialogs to be unhosted but still close with the owning web contents.
    467     // crbug.com/172502.
    468     LOG(WARNING) << "File dialog opened by panel.";
    469     return;
    470   }
    471 
    472   delegate_->RunFileChooser(tab, params);
    473 }
    474 
    475 bool ShellWindow::IsPopupOrPanel(const WebContents* source) const {
    476   return true;
    477 }
    478 
    479 void ShellWindow::MoveContents(WebContents* source, const gfx::Rect& pos) {
    480   native_app_window_->SetBounds(pos);
    481 }
    482 
    483 void ShellWindow::NavigationStateChanged(
    484     const content::WebContents* source, unsigned changed_flags) {
    485   if (changed_flags & content::INVALIDATE_TYPE_TITLE)
    486     native_app_window_->UpdateWindowTitle();
    487   else if (changed_flags & content::INVALIDATE_TYPE_TAB)
    488     native_app_window_->UpdateWindowIcon();
    489 }
    490 
    491 void ShellWindow::ToggleFullscreenModeForTab(content::WebContents* source,
    492                                              bool enter_fullscreen) {
    493   if (!IsExtensionWithPermissionOrSuggestInConsole(
    494       APIPermission::kFullscreen,
    495       extension_,
    496       source->GetRenderViewHost())) {
    497     return;
    498   }
    499 
    500   fullscreen_for_tab_ = enter_fullscreen;
    501 
    502   if (enter_fullscreen) {
    503     native_app_window_->SetFullscreen(true);
    504   } else if (!fullscreen_for_window_api_) {
    505     native_app_window_->SetFullscreen(false);
    506   }
    507 }
    508 
    509 bool ShellWindow::IsFullscreenForTabOrPending(
    510     const content::WebContents* source) const {
    511   return fullscreen_for_tab_;
    512 }
    513 
    514 void ShellWindow::Observe(int type,
    515                           const content::NotificationSource& source,
    516                           const content::NotificationDetails& details) {
    517   switch (type) {
    518     case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED: {
    519       // TODO(jianli): once http://crbug.com/123007 is fixed, we'll no longer
    520       // need to make the native window (ShellWindowViews specially) update
    521       // the clickthrough region for the new RVH.
    522       native_app_window_->RenderViewHostChanged();
    523       break;
    524     }
    525     case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
    526       const extensions::Extension* unloaded_extension =
    527           content::Details<extensions::UnloadedExtensionInfo>(
    528               details)->extension;
    529       if (extension_ == unloaded_extension)
    530         native_app_window_->Close();
    531       break;
    532     }
    533     case chrome::NOTIFICATION_APP_TERMINATING:
    534       native_app_window_->Close();
    535       break;
    536     default:
    537       NOTREACHED() << "Received unexpected notification";
    538   }
    539 }
    540 
    541 void ShellWindow::SetWebContentsBlocked(content::WebContents* web_contents,
    542                                         bool blocked) {
    543   delegate_->SetWebContentsBlocked(web_contents, blocked);
    544 }
    545 
    546 bool ShellWindow::IsWebContentsVisible(content::WebContents* web_contents) {
    547   return delegate_->IsWebContentsVisible(web_contents);
    548 }
    549 
    550 extensions::ActiveTabPermissionGranter*
    551     ShellWindow::GetActiveTabPermissionGranter() {
    552   // Shell windows don't support the activeTab permission.
    553   return NULL;
    554 }
    555 
    556 WebContentsModalDialogHost* ShellWindow::GetWebContentsModalDialogHost() {
    557   return native_app_window_.get();
    558 }
    559 
    560 void ShellWindow::AddMessageToDevToolsConsole(ConsoleMessageLevel level,
    561                                               const std::string& message) {
    562   content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
    563   rvh->Send(new ExtensionMsg_AddMessageToConsole(
    564       rvh->GetRoutingID(), level, message));
    565 }
    566 
    567 void ShellWindow::SaveWindowPosition() {
    568   if (window_key_.empty())
    569     return;
    570   if (!native_app_window_)
    571     return;
    572 
    573   ShellWindowGeometryCache* cache = ShellWindowGeometryCache::Get(profile());
    574 
    575   gfx::Rect bounds = native_app_window_->GetRestoredBounds();
    576   bounds.Inset(native_app_window_->GetFrameInsets());
    577   gfx::Rect screen_bounds =
    578       gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds).work_area();
    579   ui::WindowShowState window_state = native_app_window_->GetRestoredState();
    580   cache->SaveGeometry(extension()->id(),
    581                       window_key_,
    582                       bounds,
    583                       screen_bounds,
    584                       window_state);
    585 }
    586 
    587 void ShellWindow::AdjustBoundsToBeVisibleOnScreen(
    588     const gfx::Rect& cached_bounds,
    589     const gfx::Rect& cached_screen_bounds,
    590     const gfx::Rect& current_screen_bounds,
    591     const gfx::Size& minimum_size,
    592     gfx::Rect* bounds) const {
    593   *bounds = cached_bounds;
    594 
    595   // Reposition and resize the bounds if the cached_screen_bounds is different
    596   // from the current screen bounds and the current screen bounds doesn't
    597   // completely contain the bounds.
    598   if (cached_screen_bounds != current_screen_bounds &&
    599       !current_screen_bounds.Contains(cached_bounds)) {
    600     bounds->set_width(
    601         std::max(minimum_size.width(),
    602                  std::min(bounds->width(), current_screen_bounds.width())));
    603     bounds->set_height(
    604         std::max(minimum_size.height(),
    605                  std::min(bounds->height(), current_screen_bounds.height())));
    606     bounds->set_x(
    607         std::max(current_screen_bounds.x(),
    608                  std::min(bounds->x(),
    609                           current_screen_bounds.right() - bounds->width())));
    610     bounds->set_y(
    611         std::max(current_screen_bounds.y(),
    612                  std::min(bounds->y(),
    613                           current_screen_bounds.bottom() - bounds->height())));
    614   }
    615 }
    616 
    617 // static
    618 SkRegion* ShellWindow::RawDraggableRegionsToSkRegion(
    619       const std::vector<extensions::DraggableRegion>& regions) {
    620   SkRegion* sk_region = new SkRegion;
    621   for (std::vector<extensions::DraggableRegion>::const_iterator iter =
    622            regions.begin();
    623        iter != regions.end(); ++iter) {
    624     const extensions::DraggableRegion& region = *iter;
    625     sk_region->op(
    626         region.bounds.x(),
    627         region.bounds.y(),
    628         region.bounds.right(),
    629         region.bounds.bottom(),
    630         region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
    631   }
    632   return sk_region;
    633 }
    634 
    635 }  // namespace apps
    636