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