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