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