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