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