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
55using content::BrowserContext;
56using content::ConsoleMessageLevel;
57using content::WebContents;
58using extensions::APIPermission;
59using web_modal::WebContentsModalDialogHost;
60using web_modal::WebContentsModalDialogManager;
61
62namespace apps {
63
64namespace {
65
66const int kDefaultWidth = 512;
67const int kDefaultHeight = 384;
68
69bool IsFullscreen(int fullscreen_types) {
70  return fullscreen_types != apps::AppWindow::FULLSCREEN_TYPE_NONE;
71}
72
73void 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
82void 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.
107gfx::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.
124gfx::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
143const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN;
144
145AppWindow::BoundsSpecification::BoundsSpecification()
146    : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {}
147
148AppWindow::BoundsSpecification::~BoundsSpecification() {}
149
150void AppWindow::BoundsSpecification::ResetBounds() {
151  bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0);
152}
153
154// AppWindow::CreateParams
155
156AppWindow::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
170AppWindow::CreateParams::~CreateParams() {}
171
172gfx::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
200gfx::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
207gfx::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
214gfx::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
221gfx::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
230AppWindow::Delegate::~Delegate() {}
231
232// AppWindow
233
234AppWindow::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
256void 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
350AppWindow::~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
359void 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
371WebContents* 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
407void 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
422bool 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
450void 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
466void 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
481bool 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
489void 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
498void 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
509void 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
524void AppWindow::OnNativeWindowActivated() {
525  AppWindowRegistry::Get(browser_context_)->AppWindowActivated(this);
526}
527
528content::WebContents* AppWindow::web_contents() const {
529  return app_window_contents_->GetWebContents();
530}
531
532const extensions::Extension* AppWindow::GetExtension() const {
533  return extensions::ExtensionRegistry::Get(browser_context_)
534      ->enabled_extensions()
535      .GetByID(extension_id_);
536}
537
538NativeAppWindow* AppWindow::GetBaseWindow() { return native_app_window_.get(); }
539
540gfx::NativeWindow AppWindow::GetNativeWindow() {
541  return GetBaseWindow()->GetNativeWindow();
542}
543
544gfx::Rect AppWindow::GetClientBounds() const {
545  gfx::Rect bounds = native_app_window_->GetBounds();
546  bounds.Inset(native_app_window_->GetFrameInsets());
547  return bounds;
548}
549
550base::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
569void 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
589void 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
605void AppWindow::ClearBadge() {
606  badge_icon_image_.reset();
607  badge_icon_url_ = GURL();
608  UpdateBadgeIcon(gfx::Image());
609}
610
611void AppWindow::UpdateShape(scoped_ptr<SkRegion> region) {
612  native_app_window_->UpdateShape(region.Pass());
613}
614
615void AppWindow::UpdateDraggableRegions(
616    const std::vector<extensions::DraggableRegion>& regions) {
617  native_app_window_->UpdateDraggableRegions(regions);
618}
619
620void 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
628void 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
641void AppWindow::Maximize() { GetBaseWindow()->Maximize(); }
642
643void AppWindow::Minimize() { GetBaseWindow()->Minimize(); }
644
645void AppWindow::Restore() {
646  if (IsFullscreen(fullscreen_types_)) {
647    fullscreen_types_ = FULLSCREEN_TYPE_NONE;
648    SetNativeWindowFullscreen();
649  } else {
650    GetBaseWindow()->Restore();
651  }
652}
653
654void 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
667void AppWindow::ForcedFullscreen() {
668  fullscreen_types_ |= FULLSCREEN_TYPE_FORCED;
669  SetNativeWindowFullscreen();
670}
671
672void 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
688void 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
715void 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
726void 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
741bool AppWindow::IsAlwaysOnTop() const { return cached_always_on_top_; }
742
743void AppWindow::WindowEventsReady() {
744  can_send_events_ = true;
745  SendOnWindowShownIfShown();
746}
747
748void 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
797void AppWindow::UpdateBadgeIcon(const gfx::Image& image) {
798  badge_icon_ = image;
799  native_app_window_->UpdateBadgeIcon();
800}
801
802void 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
830void 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
836void 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
861void AppWindow::SetNativeWindowFullscreen() {
862  native_app_window_->SetFullscreen(fullscreen_types_);
863
864  if (cached_always_on_top_)
865    UpdateNativeAlwaysOnTop();
866}
867
868bool 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
890void 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
907void 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
916void AppWindow::CloseContents(WebContents* contents) {
917  native_app_window_->Close();
918}
919
920bool AppWindow::ShouldSuppressDialogs() { return true; }
921
922content::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
929void 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
942bool AppWindow::IsPopupOrPanel(const WebContents* source) const { return true; }
943
944void AppWindow::MoveContents(WebContents* source, const gfx::Rect& pos) {
945  native_app_window_->SetBounds(pos);
946}
947
948void 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
956void 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
986bool AppWindow::IsFullscreenForTabOrPending(const content::WebContents* source)
987    const {
988  return ((fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0);
989}
990
991void 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
1020void AppWindow::SetWebContentsBlocked(content::WebContents* web_contents,
1021                                      bool blocked) {
1022  delegate_->SetWebContentsBlocked(web_contents, blocked);
1023}
1024
1025bool AppWindow::IsWebContentsVisible(content::WebContents* web_contents) {
1026  return delegate_->IsWebContentsVisible(web_contents);
1027}
1028
1029WebContentsModalDialogHost* AppWindow::GetWebContentsModalDialogHost() {
1030  return native_app_window_.get();
1031}
1032
1033void 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
1040void 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
1057void 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
1087AppWindow::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
1139SkRegion* 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