chrome_native_app_window_views.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 "chrome/browser/ui/views/apps/chrome_native_app_window_views.h"
6
7#include "apps/ui/views/app_window_frame_view.h"
8#include "base/command_line.h"
9#include "chrome/app/chrome_command_ids.h"
10#include "chrome/browser/app_mode/app_mode_utils.h"
11#include "chrome/browser/chrome_page_zoom.h"
12#include "chrome/browser/favicon/favicon_tab_helper.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/host_desktop.h"
15#include "chrome/browser/ui/views/apps/shaped_app_window_targeter.h"
16#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
17#include "chrome/browser/ui/views/frame/taskbar_decorator.h"
18#include "chrome/browser/web_applications/web_app.h"
19#include "chrome/common/chrome_switches.h"
20#include "extensions/common/extension.h"
21#include "ui/aura/window.h"
22#include "ui/base/hit_test.h"
23#include "ui/base/models/simple_menu_model.h"
24#include "ui/gfx/image/image_skia.h"
25#include "ui/views/controls/menu/menu_runner.h"
26#include "ui/views/controls/webview/webview.h"
27#include "ui/views/widget/widget.h"
28#include "ui/wm/core/easy_resize_window_targeter.h"
29#include "ui/wm/core/shadow_types.h"
30
31#if defined(OS_LINUX)
32#include "chrome/browser/shell_integration_linux.h"
33#endif
34
35#if defined(USE_ASH)
36#include "ash/ash_constants.h"
37#include "ash/ash_switches.h"
38#include "ash/frame/custom_frame_view_ash.h"
39#include "ash/screen_util.h"
40#include "ash/shell.h"
41#include "ash/wm/immersive_fullscreen_controller.h"
42#include "ash/wm/panels/panel_frame_view.h"
43#include "ash/wm/window_properties.h"
44#include "ash/wm/window_state.h"
45#include "ash/wm/window_state_delegate.h"
46#include "ash/wm/window_state_observer.h"
47#include "chrome/browser/ui/ash/ash_util.h"
48#include "chrome/browser/ui/ash/multi_user/multi_user_context_menu.h"
49#include "ui/aura/client/aura_constants.h"
50#include "ui/aura/client/window_tree_client.h"
51#include "ui/aura/window_observer.h"
52#endif
53
54using apps::AppWindow;
55
56namespace {
57
58const int kMinPanelWidth = 100;
59const int kMinPanelHeight = 100;
60const int kDefaultPanelWidth = 200;
61const int kDefaultPanelHeight = 300;
62
63struct AcceleratorMapping {
64  ui::KeyboardCode keycode;
65  int modifiers;
66  int command_id;
67};
68
69const AcceleratorMapping kAppWindowAcceleratorMap[] = {
70  { ui::VKEY_W, ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
71  { ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
72  { ui::VKEY_F4, ui::EF_ALT_DOWN, IDC_CLOSE_WINDOW },
73};
74
75// These accelerators will only be available in kiosk mode. These allow the
76// user to manually zoom app windows. This is only necessary in kiosk mode
77// (in normal mode, the user can zoom via the screen magnifier).
78// TODO(xiyuan): Write a test for kiosk accelerators.
79const AcceleratorMapping kAppWindowKioskAppModeAcceleratorMap[] = {
80  { ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
81  { ui::VKEY_OEM_MINUS, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
82    IDC_ZOOM_MINUS },
83  { ui::VKEY_SUBTRACT, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
84  { ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
85  { ui::VKEY_OEM_PLUS, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
86  { ui::VKEY_ADD, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
87  { ui::VKEY_0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
88  { ui::VKEY_NUMPAD0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
89};
90
91void AddAcceleratorsFromMapping(const AcceleratorMapping mapping[],
92                                size_t mapping_length,
93                                std::map<ui::Accelerator, int>* accelerators) {
94  for (size_t i = 0; i < mapping_length; ++i) {
95    ui::Accelerator accelerator(mapping[i].keycode, mapping[i].modifiers);
96    (*accelerators)[accelerator] = mapping[i].command_id;
97  }
98}
99
100const std::map<ui::Accelerator, int>& GetAcceleratorTable() {
101  typedef std::map<ui::Accelerator, int> AcceleratorMap;
102  CR_DEFINE_STATIC_LOCAL(AcceleratorMap, accelerators, ());
103  if (accelerators.empty()) {
104    AddAcceleratorsFromMapping(
105        kAppWindowAcceleratorMap,
106        arraysize(kAppWindowAcceleratorMap),
107        &accelerators);
108
109    // Add accelerators for kiosk mode.
110    if (chrome::IsRunningInForcedAppMode()) {
111      AddAcceleratorsFromMapping(
112          kAppWindowKioskAppModeAcceleratorMap,
113          arraysize(kAppWindowKioskAppModeAcceleratorMap),
114          &accelerators);
115    }
116  }
117  return accelerators;
118}
119
120#if defined(USE_ASH)
121// This class handles a user's fullscreen request (Shift+F4/F4).
122class NativeAppWindowStateDelegate : public ash::wm::WindowStateDelegate,
123                                     public ash::wm::WindowStateObserver,
124                                     public aura::WindowObserver {
125 public:
126  NativeAppWindowStateDelegate(AppWindow* app_window,
127                               apps::NativeAppWindow* native_app_window)
128      : app_window_(app_window),
129        window_state_(
130            ash::wm::GetWindowState(native_app_window->GetNativeWindow())) {
131    // Add a window state observer to exit fullscreen properly in case
132    // fullscreen is exited without going through AppWindow::Restore(). This
133    // is the case when exiting immersive fullscreen via the "Restore" window
134    // control.
135    // TODO(pkotwicz): This is a hack. Remove ASAP. http://crbug.com/319048
136    window_state_->AddObserver(this);
137    window_state_->window()->AddObserver(this);
138  }
139  virtual ~NativeAppWindowStateDelegate() {
140    if (window_state_) {
141      window_state_->RemoveObserver(this);
142      window_state_->window()->RemoveObserver(this);
143    }
144  }
145
146 private:
147  // Overridden from ash::wm::WindowStateDelegate.
148  virtual bool ToggleFullscreen(ash::wm::WindowState* window_state) OVERRIDE {
149    // Windows which cannot be maximized should not be fullscreened.
150    DCHECK(window_state->IsFullscreen() || window_state->CanMaximize());
151    if (window_state->IsFullscreen())
152      app_window_->Restore();
153    else if (window_state->CanMaximize())
154      app_window_->OSFullscreen();
155    return true;
156  }
157
158  // Overridden from ash::wm::WindowStateObserver:
159  virtual void OnPostWindowStateTypeChange(
160      ash::wm::WindowState* window_state,
161      ash::wm::WindowStateType old_type) OVERRIDE {
162    // Since the window state might get set by a window manager, it is possible
163    // to come here before the application set its |BaseWindow|.
164    if (!window_state->IsFullscreen() && !window_state->IsMinimized() &&
165        app_window_->GetBaseWindow() &&
166        app_window_->GetBaseWindow()->IsFullscreenOrPending()) {
167      app_window_->Restore();
168      // Usually OnNativeWindowChanged() is called when the window bounds are
169      // changed as a result of a state type change. Because the change in state
170      // type has already occurred, we need to call OnNativeWindowChanged()
171      // explicitly.
172      app_window_->OnNativeWindowChanged();
173    }
174  }
175
176  // Overridden from aura::WindowObserver:
177  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
178    window_state_->RemoveObserver(this);
179    window_state_->window()->RemoveObserver(this);
180    window_state_ = NULL;
181  }
182
183  // Not owned.
184  AppWindow* app_window_;
185  ash::wm::WindowState* window_state_;
186
187  DISALLOW_COPY_AND_ASSIGN(NativeAppWindowStateDelegate);
188};
189#endif  // USE_ASH
190
191}  // namespace
192
193ChromeNativeAppWindowViews::ChromeNativeAppWindowViews()
194    : is_fullscreen_(false),
195      has_frame_color_(false),
196      active_frame_color_(SK_ColorBLACK),
197      inactive_frame_color_(SK_ColorBLACK) {
198}
199
200ChromeNativeAppWindowViews::~ChromeNativeAppWindowViews() {}
201
202void ChromeNativeAppWindowViews::OnBeforeWidgetInit(
203    views::Widget::InitParams* init_params,
204    views::Widget* widget) {}
205
206void ChromeNativeAppWindowViews::InitializeDefaultWindow(
207    const AppWindow::CreateParams& create_params) {
208  std::string app_name = web_app::GenerateApplicationNameFromExtensionId(
209      app_window()->extension_id());
210
211  views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_WINDOW);
212  init_params.delegate = this;
213  init_params.remove_standard_frame = IsFrameless() || has_frame_color_;
214  init_params.use_system_default_icon = true;
215  if (create_params.transparent_background)
216    init_params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
217  init_params.keep_on_top = create_params.always_on_top;
218
219#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
220  // Set up a custom WM_CLASS for app windows. This allows task switchers in
221  // X11 environments to distinguish them from main browser windows.
222  init_params.wm_class_name = web_app::GetWMClassFromAppName(app_name);
223  init_params.wm_class_class = shell_integration_linux::GetProgramClassName();
224  const char kX11WindowRoleApp[] = "app";
225  init_params.wm_role_name = std::string(kX11WindowRoleApp);
226#endif
227
228  OnBeforeWidgetInit(&init_params, widget());
229  widget()->Init(init_params);
230
231  // The frame insets are required to resolve the bounds specifications
232  // correctly. So we set the window bounds and constraints now.
233  gfx::Insets frame_insets = GetFrameInsets();
234  gfx::Rect window_bounds = create_params.GetInitialWindowBounds(frame_insets);
235  SetContentSizeConstraints(create_params.GetContentMinimumSize(frame_insets),
236                            create_params.GetContentMaximumSize(frame_insets));
237  if (!window_bounds.IsEmpty()) {
238    typedef apps::AppWindow::BoundsSpecification BoundsSpecification;
239    bool position_specified =
240        window_bounds.x() != BoundsSpecification::kUnspecifiedPosition &&
241        window_bounds.y() != BoundsSpecification::kUnspecifiedPosition;
242    if (!position_specified)
243      widget()->CenterWindow(window_bounds.size());
244    else
245      widget()->SetBounds(window_bounds);
246  }
247
248  if (IsFrameless() &&
249      init_params.opacity == views::Widget::InitParams::TRANSLUCENT_WINDOW &&
250      !create_params.resizable) {
251    // The given window is most likely not rectangular since it uses
252    // transparency, has no standard frame and the user cannot resize it using
253    // the OS supplied methods. Therefore we do not use a shadow for it.
254    // TODO(skuhne): If we run into an application which should have a shadow
255    // but does not have, a new attribute has to be added.
256    wm::SetShadowType(widget()->GetNativeWindow(), wm::SHADOW_TYPE_NONE);
257  }
258
259  // Register accelarators supported by app windows.
260  // TODO(jeremya/stevenjb): should these be registered for panels too?
261  views::FocusManager* focus_manager = GetFocusManager();
262  const std::map<ui::Accelerator, int>& accelerator_table =
263      GetAcceleratorTable();
264  const bool is_kiosk_app_mode = chrome::IsRunningInForcedAppMode();
265
266  // Ensures that kiosk mode accelerators are enabled when in kiosk mode (to be
267  // future proof). This is needed because GetAcceleratorTable() uses a static
268  // to store data and only checks kiosk mode once. If a platform app is
269  // launched before kiosk mode starts, the kiosk accelerators will not be
270  // registered. This DCHECK catches the case.
271  DCHECK(!is_kiosk_app_mode ||
272         accelerator_table.size() ==
273             arraysize(kAppWindowAcceleratorMap) +
274                 arraysize(kAppWindowKioskAppModeAcceleratorMap));
275
276  for (std::map<ui::Accelerator, int>::const_iterator iter =
277           accelerator_table.begin();
278       iter != accelerator_table.end(); ++iter) {
279    if (is_kiosk_app_mode && !chrome::IsCommandAllowedInAppMode(iter->second))
280      continue;
281
282    focus_manager->RegisterAccelerator(
283        iter->first, ui::AcceleratorManager::kNormalPriority, this);
284  }
285}
286
287void ChromeNativeAppWindowViews::InitializePanelWindow(
288    const AppWindow::CreateParams& create_params) {
289  views::Widget::InitParams params(views::Widget::InitParams::TYPE_PANEL);
290  params.delegate = this;
291
292  gfx::Rect initial_window_bounds =
293      create_params.GetInitialWindowBounds(gfx::Insets());
294  preferred_size_ = gfx::Size(initial_window_bounds.width(),
295                              initial_window_bounds.height());
296  if (preferred_size_.width() == 0)
297    preferred_size_.set_width(kDefaultPanelWidth);
298  else if (preferred_size_.width() < kMinPanelWidth)
299    preferred_size_.set_width(kMinPanelWidth);
300
301  if (preferred_size_.height() == 0)
302    preferred_size_.set_height(kDefaultPanelHeight);
303  else if (preferred_size_.height() < kMinPanelHeight)
304    preferred_size_.set_height(kMinPanelHeight);
305#if defined(USE_ASH)
306  if (ash::Shell::HasInstance()) {
307    // Open a new panel on the target root.
308    aura::Window* target = ash::Shell::GetTargetRootWindow();
309    params.bounds = ash::ScreenUtil::ConvertRectToScreen(
310        target, gfx::Rect(preferred_size_));
311  } else {
312    params.bounds = gfx::Rect(preferred_size_);
313  }
314#else
315  params.bounds = gfx::Rect(preferred_size_);
316#endif
317  widget()->Init(params);
318  widget()->set_focus_on_creation(create_params.focused);
319
320#if defined(USE_ASH)
321  if (create_params.state == ui::SHOW_STATE_DETACHED) {
322    gfx::Rect window_bounds(initial_window_bounds.x(),
323                            initial_window_bounds.y(),
324                            preferred_size_.width(),
325                            preferred_size_.height());
326    aura::Window* native_window = GetNativeWindow();
327    ash::wm::GetWindowState(native_window)->set_panel_attached(false);
328    aura::client::ParentWindowWithContext(native_window,
329                                          native_window->GetRootWindow(),
330                                          native_window->GetBoundsInScreen());
331    widget()->SetBounds(window_bounds);
332  }
333#else
334  // TODO(stevenjb): NativeAppWindow panels need to be implemented for other
335  // platforms.
336#endif
337}
338
339views::NonClientFrameView*
340ChromeNativeAppWindowViews::CreateStandardDesktopAppFrame() {
341  return views::WidgetDelegateView::CreateNonClientFrameView(widget());
342}
343
344apps::AppWindowFrameView*
345ChromeNativeAppWindowViews::CreateNonStandardAppFrame() {
346  apps::AppWindowFrameView* frame =
347      new apps::AppWindowFrameView(widget(),
348                                   this,
349                                   has_frame_color_,
350                                   active_frame_color_,
351                                   inactive_frame_color_);
352  frame->Init();
353#if defined(USE_ASH)
354  // For Aura windows on the Ash desktop the sizes are different and the user
355  // can resize the window from slightly outside the bounds as well.
356  if (chrome::IsNativeWindowInAsh(widget()->GetNativeWindow())) {
357    frame->SetResizeSizes(ash::kResizeInsideBoundsSize,
358                          ash::kResizeOutsideBoundsSize,
359                          ash::kResizeAreaCornerSize);
360  }
361#endif
362
363#if !defined(OS_CHROMEOS)
364  // For non-Ash windows, install an easy resize window targeter, which ensures
365  // that the root window (not the app) receives mouse events on the edges.
366  if (chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) !=
367      chrome::HOST_DESKTOP_TYPE_ASH) {
368    aura::Window* window = widget()->GetNativeWindow();
369    int resize_inside = frame->resize_inside_bounds_size();
370    gfx::Insets inset(
371        resize_inside, resize_inside, resize_inside, resize_inside);
372    // Add the EasyResizeWindowTargeter on the window, not its root window. The
373    // root window does not have a delegate, which is needed to handle the event
374    // in Linux.
375    window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
376        new wm::EasyResizeWindowTargeter(window, inset, inset)));
377  }
378#endif
379
380  return frame;
381}
382
383// ui::BaseWindow implementation.
384
385gfx::Rect ChromeNativeAppWindowViews::GetRestoredBounds() const {
386#if defined(USE_ASH)
387  gfx::Rect* bounds = widget()->GetNativeWindow()->GetProperty(
388      ash::kRestoreBoundsOverrideKey);
389  if (bounds && !bounds->IsEmpty())
390    return *bounds;
391#endif
392  return widget()->GetRestoredBounds();
393}
394
395ui::WindowShowState ChromeNativeAppWindowViews::GetRestoredState() const {
396#if !defined(USE_ASH)
397  if (IsMaximized())
398    return ui::SHOW_STATE_MAXIMIZED;
399  if (IsFullscreen())
400    return ui::SHOW_STATE_FULLSCREEN;
401#else
402  // Use kRestoreShowStateKey in case a window is minimized/hidden.
403  ui::WindowShowState restore_state = widget()->GetNativeWindow()->GetProperty(
404      aura::client::kRestoreShowStateKey);
405  if (widget()->GetNativeWindow()->GetProperty(
406          ash::kRestoreBoundsOverrideKey)) {
407    // If an override is given, we use that restore state (after filtering).
408    restore_state = widget()->GetNativeWindow()->GetProperty(
409                        ash::kRestoreShowStateOverrideKey);
410  } else {
411    // Otherwise first normal states are checked.
412    if (IsMaximized())
413      return ui::SHOW_STATE_MAXIMIZED;
414    if (IsFullscreen()) {
415      if (immersive_fullscreen_controller_.get() &&
416          immersive_fullscreen_controller_->IsEnabled()) {
417        // Restore windows which were previously in immersive fullscreen to
418        // maximized. Restoring the window to a different fullscreen type
419        // makes for a bad experience.
420        return ui::SHOW_STATE_MAXIMIZED;
421      }
422      return ui::SHOW_STATE_FULLSCREEN;
423    }
424  }
425  // Whitelist states to return so that invalid and transient states
426  // are not saved and used to restore windows when they are recreated.
427  switch (restore_state) {
428    case ui::SHOW_STATE_NORMAL:
429    case ui::SHOW_STATE_MAXIMIZED:
430    case ui::SHOW_STATE_FULLSCREEN:
431    case ui::SHOW_STATE_DETACHED:
432      return restore_state;
433
434    case ui::SHOW_STATE_DEFAULT:
435    case ui::SHOW_STATE_MINIMIZED:
436    case ui::SHOW_STATE_INACTIVE:
437    case ui::SHOW_STATE_END:
438      return ui::SHOW_STATE_NORMAL;
439  }
440#endif  // !defined(USE_ASH)
441  return ui::SHOW_STATE_NORMAL;
442}
443
444bool ChromeNativeAppWindowViews::IsAlwaysOnTop() const {
445  if (app_window()->window_type_is_panel()) {
446#if defined(USE_ASH)
447    return ash::wm::GetWindowState(widget()->GetNativeWindow())
448        ->panel_attached();
449#else
450    return true;
451#endif
452  } else {
453    return widget()->IsAlwaysOnTop();
454  }
455}
456
457// views::ContextMenuController implementation.
458
459void ChromeNativeAppWindowViews::ShowContextMenuForView(
460    views::View* source,
461    const gfx::Point& p,
462    ui::MenuSourceType source_type) {
463#if defined(USE_ASH) && defined(OS_CHROMEOS)
464  scoped_ptr<ui::MenuModel> model =
465      CreateMultiUserContextMenu(app_window()->GetNativeWindow());
466  if (!model.get())
467    return;
468
469  // Only show context menu if point is in caption.
470  gfx::Point point_in_view_coords(p);
471  views::View::ConvertPointFromScreen(widget()->non_client_view(),
472                                      &point_in_view_coords);
473  int hit_test =
474      widget()->non_client_view()->NonClientHitTest(point_in_view_coords);
475  if (hit_test == HTCAPTION) {
476    menu_runner_.reset(new views::MenuRunner(model.get()));
477    if (menu_runner_->RunMenuAt(source->GetWidget(),
478                                NULL,
479                                gfx::Rect(p, gfx::Size(0, 0)),
480                                views::MENU_ANCHOR_TOPLEFT,
481                                source_type,
482                                views::MenuRunner::HAS_MNEMONICS |
483                                    views::MenuRunner::CONTEXT_MENU) ==
484        views::MenuRunner::MENU_DELETED) {
485      return;
486    }
487  }
488#endif
489}
490
491// views::WidgetDelegate implementation.
492
493gfx::ImageSkia ChromeNativeAppWindowViews::GetWindowAppIcon() {
494  gfx::Image app_icon = app_window()->app_icon();
495  if (app_icon.IsEmpty())
496    return GetWindowIcon();
497  else
498    return *app_icon.ToImageSkia();
499}
500
501gfx::ImageSkia ChromeNativeAppWindowViews::GetWindowIcon() {
502  content::WebContents* web_contents = app_window()->web_contents();
503  if (web_contents) {
504    FaviconTabHelper* favicon_tab_helper =
505        FaviconTabHelper::FromWebContents(web_contents);
506    gfx::Image app_icon = favicon_tab_helper->GetFavicon();
507    if (!app_icon.IsEmpty())
508      return *app_icon.ToImageSkia();
509  }
510  return gfx::ImageSkia();
511}
512
513views::NonClientFrameView* ChromeNativeAppWindowViews::CreateNonClientFrameView(
514    views::Widget* widget) {
515#if defined(USE_ASH)
516  if (chrome::IsNativeViewInAsh(widget->GetNativeView())) {
517    // Set the delegate now because CustomFrameViewAsh sets the
518    // WindowStateDelegate if one is not already set.
519    ash::wm::GetWindowState(GetNativeWindow())->SetDelegate(
520        scoped_ptr<ash::wm::WindowStateDelegate>(
521            new NativeAppWindowStateDelegate(app_window(), this)).Pass());
522
523    if (app_window()->window_type_is_panel()) {
524      ash::PanelFrameView::FrameType frame_type = IsFrameless() ?
525          ash::PanelFrameView::FRAME_NONE : ash::PanelFrameView::FRAME_ASH;
526      views::NonClientFrameView* frame_view =
527          new ash::PanelFrameView(widget, frame_type);
528      frame_view->set_context_menu_controller(this);
529      return frame_view;
530    }
531
532    if (IsFrameless())
533      return CreateNonStandardAppFrame();
534
535    ash::CustomFrameViewAsh* custom_frame_view =
536        new ash::CustomFrameViewAsh(widget);
537#if defined(OS_CHROMEOS)
538    // Non-frameless app windows can be put into immersive fullscreen.
539    // TODO(pkotwicz): Investigate if immersive fullscreen can be enabled for
540    // Windows Ash.
541    immersive_fullscreen_controller_.reset(
542        new ash::ImmersiveFullscreenController());
543    custom_frame_view->InitImmersiveFullscreenControllerForView(
544        immersive_fullscreen_controller_.get());
545#endif
546    custom_frame_view->GetHeaderView()->set_context_menu_controller(this);
547    return custom_frame_view;
548  }
549#endif
550  return (IsFrameless() || has_frame_color_) ?
551      CreateNonStandardAppFrame() : CreateStandardDesktopAppFrame();
552}
553
554bool ChromeNativeAppWindowViews::WidgetHasHitTestMask() const {
555  return shape_ != NULL;
556}
557
558void ChromeNativeAppWindowViews::GetWidgetHitTestMask(gfx::Path* mask) const {
559  shape_->getBoundaryPath(mask);
560}
561
562// views::View implementation.
563
564gfx::Size ChromeNativeAppWindowViews::GetPreferredSize() const {
565  if (!preferred_size_.IsEmpty())
566    return preferred_size_;
567  return NativeAppWindowViews::GetPreferredSize();
568}
569
570bool ChromeNativeAppWindowViews::AcceleratorPressed(
571    const ui::Accelerator& accelerator) {
572  const std::map<ui::Accelerator, int>& accelerator_table =
573      GetAcceleratorTable();
574  std::map<ui::Accelerator, int>::const_iterator iter =
575      accelerator_table.find(accelerator);
576  DCHECK(iter != accelerator_table.end());
577  int command_id = iter->second;
578  switch (command_id) {
579    case IDC_CLOSE_WINDOW:
580      Close();
581      return true;
582    case IDC_ZOOM_MINUS:
583      chrome_page_zoom::Zoom(web_view()->GetWebContents(),
584                             content::PAGE_ZOOM_OUT);
585      return true;
586    case IDC_ZOOM_NORMAL:
587      chrome_page_zoom::Zoom(web_view()->GetWebContents(),
588                             content::PAGE_ZOOM_RESET);
589      return true;
590    case IDC_ZOOM_PLUS:
591      chrome_page_zoom::Zoom(web_view()->GetWebContents(),
592                             content::PAGE_ZOOM_IN);
593      return true;
594    default:
595      NOTREACHED() << "Unknown accelerator sent to app window.";
596  }
597  return NativeAppWindowViews::AcceleratorPressed(accelerator);
598}
599
600// NativeAppWindow implementation.
601
602void ChromeNativeAppWindowViews::SetFullscreen(int fullscreen_types) {
603  // Fullscreen not supported by panels.
604  if (app_window()->window_type_is_panel())
605    return;
606  is_fullscreen_ = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
607  widget()->SetFullscreen(is_fullscreen_);
608
609#if defined(USE_ASH)
610  if (immersive_fullscreen_controller_.get()) {
611    // |immersive_fullscreen_controller_| should only be set if immersive
612    // fullscreen is the fullscreen type used by the OS.
613    immersive_fullscreen_controller_->SetEnabled(
614        ash::ImmersiveFullscreenController::WINDOW_TYPE_PACKAGED_APP,
615        (fullscreen_types & AppWindow::FULLSCREEN_TYPE_OS) != 0);
616    // Autohide the shelf instead of hiding the shelf completely when only in
617    // OS fullscreen.
618    ash::wm::WindowState* window_state =
619        ash::wm::GetWindowState(widget()->GetNativeWindow());
620    window_state->set_hide_shelf_when_fullscreen(fullscreen_types !=
621                                                 AppWindow::FULLSCREEN_TYPE_OS);
622    DCHECK(ash::Shell::HasInstance());
623    ash::Shell::GetInstance()->UpdateShelfVisibility();
624  }
625#endif
626
627  // TODO(jeremya) we need to call RenderViewHost::ExitFullscreen() if we
628  // ever drop the window out of fullscreen in response to something that
629  // wasn't the app calling webkitCancelFullScreen().
630}
631
632bool ChromeNativeAppWindowViews::IsFullscreenOrPending() const {
633  return is_fullscreen_;
634}
635
636bool ChromeNativeAppWindowViews::IsDetached() const {
637  if (!app_window()->window_type_is_panel())
638    return false;
639#if defined(USE_ASH)
640  return !ash::wm::GetWindowState(widget()->GetNativeWindow())
641              ->panel_attached();
642#else
643  return false;
644#endif
645}
646
647void ChromeNativeAppWindowViews::UpdateBadgeIcon() {
648  const gfx::Image* icon = NULL;
649  if (!app_window()->badge_icon().IsEmpty()) {
650    icon = &app_window()->badge_icon();
651    // chrome::DrawTaskbarDecoration can do interesting things with non-square
652    // bitmaps.
653    // TODO(benwells): Refactor chrome::DrawTaskbarDecoration to not be avatar
654    // specific, and lift this restriction.
655    if (icon->Width() != icon->Height()) {
656      LOG(ERROR) << "Attempt to set a non-square badge; request ignored.";
657      return;
658    }
659  }
660  chrome::DrawTaskbarDecoration(GetNativeWindow(), icon);
661}
662
663void ChromeNativeAppWindowViews::UpdateShape(scoped_ptr<SkRegion> region) {
664  bool had_shape = shape_;
665  shape_ = region.Pass();
666
667  aura::Window* native_window = widget()->GetNativeWindow();
668  if (shape_) {
669    widget()->SetShape(new SkRegion(*shape_));
670    if (!had_shape) {
671      native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
672          new ShapedAppWindowTargeter(native_window, this)));
673    }
674  } else {
675    widget()->SetShape(NULL);
676    if (had_shape)
677      native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>());
678  }
679}
680
681bool ChromeNativeAppWindowViews::HasFrameColor() const {
682  return has_frame_color_;
683}
684
685SkColor ChromeNativeAppWindowViews::ActiveFrameColor() const {
686  return active_frame_color_;
687}
688
689SkColor ChromeNativeAppWindowViews::InactiveFrameColor() const {
690  return inactive_frame_color_;
691}
692
693// NativeAppWindowViews implementation.
694
695void ChromeNativeAppWindowViews::InitializeWindow(
696    AppWindow* app_window,
697    const AppWindow::CreateParams& create_params) {
698  DCHECK(widget());
699  has_frame_color_ = create_params.has_frame_color;
700  active_frame_color_ = create_params.active_frame_color;
701  inactive_frame_color_ = create_params.inactive_frame_color;
702  if (create_params.window_type == AppWindow::WINDOW_TYPE_PANEL ||
703      create_params.window_type == AppWindow::WINDOW_TYPE_V1_PANEL) {
704    InitializePanelWindow(create_params);
705  } else {
706    InitializeDefaultWindow(create_params);
707  }
708  extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
709      Profile::FromBrowserContext(app_window->browser_context()),
710      widget()->GetFocusManager(),
711      extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
712      NULL));
713}
714