1// Copyright (c) 2011 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/gtk/browser_window_gtk.h"
6
7#include <gdk/gdkkeysyms.h>
8
9#include <dlfcn.h>
10#include <string>
11
12#include "base/base_paths.h"
13#include "base/command_line.h"
14#include "base/environment.h"
15#include "base/i18n/file_util_icu.h"
16#include "base/logging.h"
17#include "base/memory/scoped_ptr.h"
18#include "base/memory/singleton.h"
19#include "base/message_loop.h"
20#include "base/nix/xdg_util.h"
21#include "base/path_service.h"
22#include "base/string_util.h"
23#include "base/time.h"
24#include "base/utf_string_conversions.h"
25#include "chrome/app/chrome_command_ids.h"
26#include "chrome/browser/autocomplete/autocomplete_edit_view.h"
27#include "chrome/browser/bookmarks/bookmark_utils.h"
28#include "chrome/browser/browser_process.h"
29#include "chrome/browser/debugger/devtools_window.h"
30#include "chrome/browser/download/download_item_model.h"
31#include "chrome/browser/download/download_manager.h"
32#include "chrome/browser/page_info_window.h"
33#include "chrome/browser/prefs/pref_service.h"
34#include "chrome/browser/prefs/scoped_user_pref_update.h"
35#include "chrome/browser/profiles/profile.h"
36#include "chrome/browser/tabs/tab_strip_model.h"
37#include "chrome/browser/themes/theme_service.h"
38#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
39#include "chrome/browser/ui/browser.h"
40#include "chrome/browser/ui/browser_dialogs.h"
41#include "chrome/browser/ui/browser_list.h"
42#include "chrome/browser/ui/find_bar/find_bar_controller.h"
43#include "chrome/browser/ui/find_bar/find_tab_helper.h"
44#include "chrome/browser/ui/gtk/about_chrome_dialog.h"
45#include "chrome/browser/ui/gtk/accelerators_gtk.h"
46#include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.h"
47#include "chrome/browser/ui/gtk/browser_titlebar.h"
48#include "chrome/browser/ui/gtk/browser_toolbar_gtk.h"
49#include "chrome/browser/ui/gtk/cairo_cached_surface.h"
50#include "chrome/browser/ui/gtk/collected_cookies_gtk.h"
51#include "chrome/browser/ui/gtk/create_application_shortcuts_dialog_gtk.h"
52#include "chrome/browser/ui/gtk/download/download_in_progress_dialog_gtk.h"
53#include "chrome/browser/ui/gtk/download/download_shelf_gtk.h"
54#include "chrome/browser/ui/gtk/edit_search_engine_dialog.h"
55#include "chrome/browser/ui/gtk/find_bar_gtk.h"
56#include "chrome/browser/ui/gtk/fullscreen_exit_bubble_gtk.h"
57#include "chrome/browser/ui/gtk/global_menu_bar.h"
58#include "chrome/browser/ui/gtk/gtk_floating_container.h"
59#include "chrome/browser/ui/gtk/gtk_theme_service.h"
60#include "chrome/browser/ui/gtk/gtk_util.h"
61#include "chrome/browser/ui/gtk/info_bubble_gtk.h"
62#include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h"
63#include "chrome/browser/ui/gtk/infobars/infobar_gtk.h"
64#include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
65#include "chrome/browser/ui/gtk/nine_box.h"
66#include "chrome/browser/ui/gtk/reload_button_gtk.h"
67#include "chrome/browser/ui/gtk/repost_form_warning_gtk.h"
68#include "chrome/browser/ui/gtk/status_bubble_gtk.h"
69#include "chrome/browser/ui/gtk/tab_contents_container_gtk.h"
70#include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
71#include "chrome/browser/ui/gtk/task_manager_gtk.h"
72#include "chrome/browser/ui/gtk/theme_install_bubble_view_gtk.h"
73#include "chrome/browser/ui/gtk/update_recommended_dialog.h"
74#include "chrome/browser/ui/omnibox/location_bar.h"
75#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
76#include "chrome/browser/ui/webui/bug_report_ui.h"
77#include "chrome/browser/ui/window_sizer.h"
78#include "chrome/browser/web_applications/web_app.h"
79#include "chrome/common/chrome_switches.h"
80#include "chrome/common/pref_names.h"
81#include "content/browser/renderer_host/render_widget_host_view.h"
82#include "content/browser/tab_contents/tab_contents.h"
83#include "content/browser/tab_contents/tab_contents_view.h"
84#include "content/common/native_web_keyboard_event.h"
85#include "content/common/notification_service.h"
86#include "grit/app_resources.h"
87#include "grit/chromium_strings.h"
88#include "grit/generated_resources.h"
89#include "grit/theme_resources.h"
90#include "ui/base/keycodes/keyboard_codes.h"
91#include "ui/base/l10n/l10n_util.h"
92#include "ui/gfx/gtk_util.h"
93#include "ui/gfx/rect.h"
94#include "ui/gfx/skia_utils_gtk.h"
95
96namespace {
97
98// The number of milliseconds between loading animation frames.
99const int kLoadingAnimationFrameTimeMs = 30;
100
101// Default height of dev tools pane when docked to the browser window.  This
102// matches the value in Views.
103const int kDefaultDevToolsHeight = 200;
104
105const int kMinDevToolsHeight = 50;
106
107const char* kBrowserWindowKey = "__BROWSER_WINDOW_GTK__";
108
109// The frame border is only visible in restored mode and is hardcoded to 4 px
110// on each side regardless of the system window border size.
111const int kFrameBorderThickness = 4;
112// While resize areas on Windows are normally the same size as the window
113// borders, our top area is shrunk by 1 px to make it easier to move the window
114// around with our thinner top grabbable strip.  (Incidentally, our side and
115// bottom resize areas don't match the frame border thickness either -- they
116// span the whole nonclient area, so there's no "dead zone" for the mouse.)
117const int kTopResizeAdjust = 1;
118// In the window corners, the resize areas don't actually expand bigger, but
119// the 16 px at the end of each edge triggers diagonal resizing.
120const int kResizeAreaCornerSize = 16;
121// The thickness of the shadow around the toolbar+web content area.  There are
122// actually a couple pixels more that should overlap the toolbar and web
123// content area, but we don't use those pixels.
124const int kContentShadowThickness = 2;
125// The offset to the background when the custom frame is off.  We want the
126// window background to line up with the tab background regardless of whether
127// we're in custom frame mode or not.  Since themes are designed with the
128// custom frame in mind, we need to offset the background when the custom frame
129// is off.
130const int kCustomFrameBackgroundVerticalOffset = 15;
131
132// The timeout in milliseconds before we'll get the true window position with
133// gtk_window_get_position() after the last GTK configure-event signal.
134const int kDebounceTimeoutMilliseconds = 100;
135
136// Ubuntu patches their verrsion of GTK+ so that there is always a
137// gripper in the bottom right corner of the window. We dynamically
138// look up this symbol because it's a non-standard Ubuntu extension to
139// GTK+. We always need to disable this feature since we can't
140// communicate this to WebKit easily.
141typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean);
142gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym;
143
144void  EnsureResizeGripFunction() {
145  static bool resize_grip_looked_up = false;
146  if (!resize_grip_looked_up) {
147    resize_grip_looked_up = true;
148    gtk_window_set_has_resize_grip_sym =
149        reinterpret_cast<gtk_window_set_has_resize_grip_func>(
150            dlsym(NULL, "gtk_window_set_has_resize_grip"));
151  }
152}
153
154// Using gtk_window_get_position/size creates a race condition, so only use
155// this to get the initial bounds.  After window creation, we pick up the
156// normal bounds by connecting to the configure-event signal.
157gfx::Rect GetInitialWindowBounds(GtkWindow* window) {
158  gint x, y, width, height;
159  gtk_window_get_position(window, &x, &y);
160  gtk_window_get_size(window, &width, &height);
161  return gfx::Rect(x, y, width, height);
162}
163
164// Get the command ids of the key combinations that are not valid gtk
165// accelerators.
166int GetCustomCommandId(GdkEventKey* event) {
167  // Filter modifier to only include accelerator modifiers.
168  guint modifier = event->state & gtk_accelerator_get_default_mod_mask();
169  switch (event->keyval) {
170    // Gtk doesn't allow GDK_Tab or GDK_ISO_Left_Tab to be an accelerator (see
171    // gtk_accelerator_valid), so we need to handle these accelerators
172    // manually.
173    // Some X clients (e.g. cygwin, NX client, etc.) also send GDK_KP_Tab when
174    // typing a tab key. We should also handle GDK_KP_Tab for such X clients as
175    // Firefox does.
176    case GDK_Tab:
177    case GDK_ISO_Left_Tab:
178    case GDK_KP_Tab:
179      if (GDK_CONTROL_MASK == modifier) {
180        return IDC_SELECT_NEXT_TAB;
181      } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
182        return IDC_SELECT_PREVIOUS_TAB;
183      }
184      break;
185
186    default:
187      break;
188  }
189  return -1;
190}
191
192// Get the command ids of the accelerators that we don't want the native widget
193// to be able to override.
194int GetPreHandleCommandId(GdkEventKey* event) {
195  // Filter modifier to only include accelerator modifiers.
196  guint modifier = event->state & gtk_accelerator_get_default_mod_mask();
197  switch (event->keyval) {
198    case GDK_Page_Down:
199      if (GDK_CONTROL_MASK == modifier) {
200        return IDC_SELECT_NEXT_TAB;
201      } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
202        return IDC_MOVE_TAB_NEXT;
203      }
204      break;
205
206    case GDK_Page_Up:
207      if (GDK_CONTROL_MASK == modifier) {
208        return IDC_SELECT_PREVIOUS_TAB;
209      } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
210        return IDC_MOVE_TAB_PREVIOUS;
211      }
212      break;
213
214    default:
215      break;
216  }
217  return -1;
218}
219
220GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) {
221  switch (edge) {
222    case GDK_WINDOW_EDGE_NORTH_WEST:
223      return GDK_TOP_LEFT_CORNER;
224    case GDK_WINDOW_EDGE_NORTH:
225      return GDK_TOP_SIDE;
226    case GDK_WINDOW_EDGE_NORTH_EAST:
227      return GDK_TOP_RIGHT_CORNER;
228    case GDK_WINDOW_EDGE_WEST:
229      return GDK_LEFT_SIDE;
230    case GDK_WINDOW_EDGE_EAST:
231      return GDK_RIGHT_SIDE;
232    case GDK_WINDOW_EDGE_SOUTH_WEST:
233      return GDK_BOTTOM_LEFT_CORNER;
234    case GDK_WINDOW_EDGE_SOUTH:
235      return GDK_BOTTOM_SIDE;
236    case GDK_WINDOW_EDGE_SOUTH_EAST:
237      return GDK_BOTTOM_RIGHT_CORNER;
238    default:
239      NOTREACHED();
240  }
241  return GDK_LAST_CURSOR;
242}
243
244// A helper method for setting the GtkWindow size that should be used in place
245// of calling gtk_window_resize directly.  This is done to avoid a WM "feature"
246// where setting the window size to the monitor size causes the WM to set the
247// EWMH for full screen mode.
248void SetWindowSize(GtkWindow* window, const gfx::Size& size) {
249  GdkScreen* screen = gtk_window_get_screen(window);
250  gint num_monitors = gdk_screen_get_n_monitors(screen);
251  // Make sure the window doesn't match any monitor size.  We compare against
252  // all monitors because we don't know which monitor the window is going to
253  // open on (the WM decides that).
254  for (gint i = 0; i < num_monitors; ++i) {
255    GdkRectangle monitor_size;
256    gdk_screen_get_monitor_geometry(screen, i, &monitor_size);
257    if (gfx::Size(monitor_size.width, monitor_size.height) == size) {
258      gtk_window_resize(window, size.width(), size.height() - 1);
259      return;
260    }
261  }
262  gtk_window_resize(window, size.width(), size.height());
263}
264
265GQuark GetBrowserWindowQuarkKey() {
266  static GQuark quark = g_quark_from_static_string(kBrowserWindowKey);
267  return quark;
268}
269
270}  // namespace
271
272std::map<XID, GtkWindow*> BrowserWindowGtk::xid_map_;
273
274BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
275    :  browser_(browser),
276       state_(GDK_WINDOW_STATE_WITHDRAWN),
277       bookmark_bar_is_floating_(false),
278       frame_cursor_(NULL),
279       is_active_(!ui::ActiveWindowWatcherX::WMSupportsActivation()),
280       last_click_time_(0),
281       maximize_after_show_(false),
282       suppress_window_raise_(false),
283       accel_group_(NULL),
284       debounce_timer_disabled_(false),
285       infobar_arrow_model_(this) {
286}
287
288BrowserWindowGtk::~BrowserWindowGtk() {
289  ui::ActiveWindowWatcherX::RemoveObserver(this);
290
291  browser_->tabstrip_model()->RemoveObserver(this);
292}
293
294void BrowserWindowGtk::Init() {
295  // We register first so that other views like the toolbar can use the
296  // is_active() function in their ActiveWindowChanged() handlers.
297  ui::ActiveWindowWatcherX::AddObserver(this);
298
299  use_custom_frame_pref_.Init(prefs::kUseCustomChromeFrame,
300      browser_->profile()->GetPrefs(), this);
301
302  // In some (older) versions of compiz, raising top-level windows when they
303  // are partially off-screen causes them to get snapped back on screen, not
304  // always even on the current virtual desktop.  If we are running under
305  // compiz, suppress such raises, as they are not necessary in compiz anyway.
306  std::string wm_name;
307  if (ui::GetWindowManagerName(&wm_name) && wm_name == "compiz")
308    suppress_window_raise_ = true;
309
310  window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
311  g_object_set_qdata(G_OBJECT(window_), GetBrowserWindowQuarkKey(), this);
312  gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK |
313                                             GDK_POINTER_MOTION_MASK);
314
315  // Disable the resize gripper on Ubuntu.
316  EnsureResizeGripFunction();
317  if (gtk_window_set_has_resize_grip_sym)
318    gtk_window_set_has_resize_grip_sym(GTK_WINDOW(window_), FALSE);
319
320  // Add this window to its own unique window group to allow for
321  // window-to-parent modality.
322  gtk_window_group_add_window(gtk_window_group_new(), window_);
323  g_object_unref(gtk_window_get_group(window_));
324
325  if (browser_->type() & Browser::TYPE_APP) {
326    std::string app_name = browser_->app_name();
327    if (app_name != DevToolsWindow::kDevToolsApp) {
328      std::string wmclassname = web_app::GetWMClassFromAppName(app_name);
329
330      scoped_ptr<base::Environment> env(base::Environment::Create());
331      if (base::nix::GetDesktopEnvironment(env.get()) ==
332          base::nix::DESKTOP_ENVIRONMENT_XFCE) {
333        // Workaround for XFCE. XFCE seems to treat the class as a user
334        // displayed title, which our app name certainly isn't. They don't have
335        // a dock or application based behaviour so do what looks good.
336        gtk_window_set_wmclass(window_,
337                               wmclassname.c_str(),
338                               gdk_get_program_class());
339      } else {
340        // Most everything else uses the wmclass_class to group windows
341        // together (docks, per application stuff, etc). Hopefully they won't
342        // display wmclassname to the user.
343        gtk_window_set_wmclass(window_,
344                               g_get_prgname(),
345                               wmclassname.c_str());
346      }
347
348      gtk_window_set_role(window_, wmclassname.c_str());
349    }
350  }
351
352  // For popups, we initialize widgets then set the window geometry, because
353  // popups need the widgets inited before they can set the window size
354  // properly. For other windows, we set the geometry first to prevent resize
355  // flicker.
356  if (browser_->type() & Browser::TYPE_POPUP) {
357    InitWidgets();
358    SetGeometryHints();
359  } else {
360    SetGeometryHints();
361    InitWidgets();
362  }
363
364  ConnectAccelerators();
365
366  // Set the initial background color of widgets.
367  SetBackgroundColor();
368  HideUnsupportedWindowFeatures();
369
370  registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
371                 NotificationService::AllSources());
372}
373
374gboolean BrowserWindowGtk::OnCustomFrameExpose(GtkWidget* widget,
375                                               GdkEventExpose* event) {
376  // Draw the default background.
377  cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
378  gdk_cairo_rectangle(cr, &event->area);
379  cairo_clip(cr);
380
381  if (UsingCustomPopupFrame()) {
382    DrawPopupFrame(cr, widget, event);
383  } else {
384    DrawCustomFrame(cr, widget, event);
385  }
386
387  DrawContentShadow(cr);
388
389  cairo_destroy(cr);
390
391  if (UseCustomFrame() && !IsMaximized()) {
392    static NineBox custom_frame_border(
393        IDR_WINDOW_TOP_LEFT_CORNER,
394        IDR_WINDOW_TOP_CENTER,
395        IDR_WINDOW_TOP_RIGHT_CORNER,
396        IDR_WINDOW_LEFT_SIDE,
397        0,
398        IDR_WINDOW_RIGHT_SIDE,
399        IDR_WINDOW_BOTTOM_LEFT_CORNER,
400        IDR_WINDOW_BOTTOM_CENTER,
401        IDR_WINDOW_BOTTOM_RIGHT_CORNER);
402
403    custom_frame_border.RenderToWidget(widget);
404  }
405
406  return FALSE;  // Allow subwidgets to paint.
407}
408
409void BrowserWindowGtk::DrawContentShadow(cairo_t* cr) {
410  // Draw the shadow above the toolbar. Tabs on the tabstrip will draw over us.
411  GtkThemeService* theme_provider = GtkThemeService::GetFrom(
412      browser()->profile());
413  int left_x, top_y;
414  gtk_widget_translate_coordinates(toolbar_->widget(),
415      GTK_WIDGET(window_), 0, 0, &left_x,
416      &top_y);
417  int center_width = window_vbox_->allocation.width;
418
419  CairoCachedSurface* top_center = theme_provider->GetSurfaceNamed(
420      IDR_CONTENT_TOP_CENTER, GTK_WIDGET(window_));
421  CairoCachedSurface* top_right = theme_provider->GetSurfaceNamed(
422      IDR_CONTENT_TOP_RIGHT_CORNER, GTK_WIDGET(window_));
423  CairoCachedSurface* top_left = theme_provider->GetSurfaceNamed(
424      IDR_CONTENT_TOP_LEFT_CORNER, GTK_WIDGET(window_));
425
426  int center_left_x = left_x;
427  if (ShouldDrawContentDropShadow()) {
428    // Don't draw over the corners.
429    center_left_x += top_left->Width() - kContentShadowThickness;
430    center_width -= (top_left->Width() + top_right->Width());
431    center_width += 2 * kContentShadowThickness;
432  }
433
434  top_center->SetSource(cr, center_left_x, top_y - kContentShadowThickness);
435  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
436  cairo_rectangle(cr, center_left_x, top_y - kContentShadowThickness,
437                  center_width, top_center->Height());
438  cairo_fill(cr);
439
440  // Only draw the rest of the shadow if the user has the custom frame enabled
441  // and the browser is not maximized.
442  if (!ShouldDrawContentDropShadow())
443    return;
444
445  // The top left corner has a width of 3 pixels. On Windows, the last column
446  // of pixels overlap the toolbar. We just crop it off on Linux.  The top
447  // corners extend to the base of the toolbar (one pixel above the dividing
448  // line).
449  int right_x = center_left_x + center_width;
450  top_left->SetSource(
451      cr, left_x - kContentShadowThickness, top_y - kContentShadowThickness);
452  // The toolbar is shorter in location bar only mode so clip the image to the
453  // height of the toolbar + the amount of shadow above the toolbar.
454  cairo_rectangle(cr,
455      left_x - kContentShadowThickness,
456      top_y - kContentShadowThickness,
457      top_left->Width(),
458      top_left->Height());
459  cairo_fill(cr);
460
461  // Likewise, we crop off the left column of pixels for the top right corner.
462  top_right->SetSource(cr, right_x, top_y - kContentShadowThickness);
463  cairo_rectangle(cr,
464      right_x,
465      top_y - kContentShadowThickness,
466      top_right->Width(),
467      top_right->Height());
468  cairo_fill(cr);
469
470  // Fill in the sides.  As above, we only draw 2 of the 3 columns on Linux.
471  int bottom_y;
472  gtk_widget_translate_coordinates(window_vbox_,
473      GTK_WIDGET(window_),
474      0, window_vbox_->allocation.height,
475      NULL, &bottom_y);
476  // |side_y| is where to start drawing the side shadows.  The top corners draw
477  // the sides down to the bottom of the toolbar.
478  int side_y = top_y - kContentShadowThickness + top_right->Height();
479  // |side_height| is how many pixels to draw for the side borders.  We do one
480  // pixel before the bottom of the web contents because that extra pixel is
481  // drawn by the bottom corners.
482  int side_height = bottom_y - side_y - 1;
483  if (side_height > 0) {
484    CairoCachedSurface* left = theme_provider->GetSurfaceNamed(
485        IDR_CONTENT_LEFT_SIDE, GTK_WIDGET(window_));
486    left->SetSource(cr, left_x - kContentShadowThickness, side_y);
487    cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
488    cairo_rectangle(cr,
489        left_x - kContentShadowThickness,
490        side_y,
491        kContentShadowThickness,
492        side_height);
493    cairo_fill(cr);
494
495    CairoCachedSurface* right = theme_provider->GetSurfaceNamed(
496        IDR_CONTENT_RIGHT_SIDE, GTK_WIDGET(window_));
497    int right_side_x =
498        right_x + top_right->Width() - kContentShadowThickness - 1;
499    right->SetSource(cr, right_side_x, side_y);
500    cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
501    cairo_rectangle(cr,
502        right_side_x,
503        side_y,
504        kContentShadowThickness,
505        side_height);
506    cairo_fill(cr);
507  }
508
509  // Draw the bottom corners.  The bottom corners also draw the bottom row of
510  // pixels of the side shadows.
511  CairoCachedSurface* bottom_left = theme_provider->GetSurfaceNamed(
512      IDR_CONTENT_BOTTOM_LEFT_CORNER, GTK_WIDGET(window_));
513  bottom_left->SetSource(cr, left_x - kContentShadowThickness, bottom_y - 1);
514  cairo_paint(cr);
515
516  CairoCachedSurface* bottom_right = theme_provider->GetSurfaceNamed(
517      IDR_CONTENT_BOTTOM_RIGHT_CORNER, GTK_WIDGET(window_));
518  bottom_right->SetSource(cr, right_x - 1, bottom_y - 1);
519  cairo_paint(cr);
520
521  // Finally, draw the bottom row. Since we don't overlap the contents, we clip
522  // the top row of pixels.
523  CairoCachedSurface* bottom = theme_provider->GetSurfaceNamed(
524      IDR_CONTENT_BOTTOM_CENTER, GTK_WIDGET(window_));
525  bottom->SetSource(cr, left_x + 1, bottom_y - 1);
526  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
527  cairo_rectangle(cr,
528      left_x + 1,
529      bottom_y,
530      window_vbox_->allocation.width - 2,
531      kContentShadowThickness);
532  cairo_fill(cr);
533}
534
535void BrowserWindowGtk::DrawPopupFrame(cairo_t* cr,
536                                      GtkWidget* widget,
537                                      GdkEventExpose* event) {
538  GtkThemeService* theme_provider = GtkThemeService::GetFrom(
539      browser()->profile());
540
541  // Like DrawCustomFrame(), except that we use the unthemed resources to draw
542  // the background. We do this because we can't rely on sane images in the
543  // theme that we can draw text on. (We tried using the tab background, but
544  // that has inverse saturation from what the user usually expects).
545  int image_name = GetThemeFrameResource();
546  CairoCachedSurface* surface = theme_provider->GetUnthemedSurfaceNamed(
547      image_name, widget);
548  surface->SetSource(cr, 0, GetVerticalOffset());
549  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REFLECT);
550  cairo_rectangle(cr, event->area.x, event->area.y,
551                  event->area.width, event->area.height);
552  cairo_fill(cr);
553}
554
555void BrowserWindowGtk::DrawCustomFrame(cairo_t* cr,
556                                       GtkWidget* widget,
557                                       GdkEventExpose* event) {
558  GtkThemeService* theme_provider = GtkThemeService::GetFrom(
559      browser()->profile());
560
561  int image_name = GetThemeFrameResource();
562
563  CairoCachedSurface* surface = theme_provider->GetSurfaceNamed(
564      image_name, widget);
565  if (event->area.y < surface->Height()) {
566    surface->SetSource(cr, 0, GetVerticalOffset());
567
568    // The frame background isn't tiled vertically.
569    cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
570    cairo_rectangle(cr, event->area.x, event->area.y,
571                    event->area.width, surface->Height() - event->area.y);
572    cairo_fill(cr);
573  }
574
575  if (theme_provider->HasCustomImage(IDR_THEME_FRAME_OVERLAY) &&
576      !browser()->profile()->IsOffTheRecord()) {
577    CairoCachedSurface* theme_overlay = theme_provider->GetSurfaceNamed(
578        IsActive() ? IDR_THEME_FRAME_OVERLAY
579        : IDR_THEME_FRAME_OVERLAY_INACTIVE, widget);
580    theme_overlay->SetSource(cr, 0, GetVerticalOffset());
581    cairo_paint(cr);
582  }
583}
584
585int BrowserWindowGtk::GetVerticalOffset() {
586  return (IsMaximized() || (!UseCustomFrame())) ?
587      -kCustomFrameBackgroundVerticalOffset : 0;
588}
589
590int BrowserWindowGtk::GetThemeFrameResource() {
591  bool incognito = browser()->profile()->IsOffTheRecord();
592  int image_name;
593  if (IsActive()) {
594    image_name = incognito ? IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
595  } else {
596    image_name = incognito ? IDR_THEME_FRAME_INCOGNITO_INACTIVE :
597                 IDR_THEME_FRAME_INACTIVE;
598  }
599
600  return image_name;
601}
602
603void BrowserWindowGtk::Show() {
604  // The Browser associated with this browser window must become the active
605  // browser at the time Show() is called. This is the natural behaviour under
606  // Windows, but gtk_widget_show won't show the widget (and therefore won't
607  // call OnFocusIn()) until we return to the runloop. Therefore any calls to
608  // BrowserList::GetLastActive() (for example, in bookmark_util), will return
609  // the previous browser instead if we don't explicitly set it here.
610  BrowserList::SetLastActive(browser());
611
612  gtk_window_present(window_);
613  if (maximize_after_show_) {
614    gtk_window_maximize(window_);
615    maximize_after_show_ = false;
616  }
617
618  // If we have sized the window by setting a size request for the render
619  // area, then undo it so that the render view can later adjust its own
620  // size.
621  gtk_widget_set_size_request(contents_container_->widget(), -1, -1);
622}
623
624void BrowserWindowGtk::ShowInactive() {
625  gtk_window_set_focus_on_map(window_, false);
626  gtk_widget_show(GTK_WIDGET(window_));
627}
628
629void BrowserWindowGtk::SetBoundsImpl(const gfx::Rect& bounds,
630                                     bool exterior,
631                                     bool move) {
632  gint x = static_cast<gint>(bounds.x());
633  gint y = static_cast<gint>(bounds.y());
634  gint width = static_cast<gint>(bounds.width());
635  gint height = static_cast<gint>(bounds.height());
636
637  if (move)
638    gtk_window_move(window_, x, y);
639
640  if (exterior) {
641    SetWindowSize(window_, gfx::Size(width, height));
642  } else {
643    gtk_widget_set_size_request(contents_container_->widget(),
644                                width, height);
645  }
646}
647
648void BrowserWindowGtk::SetBounds(const gfx::Rect& bounds) {
649  if (IsFullscreen())
650    SetFullscreen(false);
651  SetBoundsImpl(bounds, true, true);
652}
653
654void BrowserWindowGtk::Close() {
655  // We're already closing.  Do nothing.
656  if (!window_)
657    return;
658
659  if (!CanClose())
660    return;
661
662  // We're going to destroy the window, make sure the tab strip isn't running
663  // any animations which may still reference GtkWidgets.
664  tabstrip_->StopAnimation();
665
666  SaveWindowPosition();
667
668  if (accel_group_) {
669    // Disconnecting the keys we connected to our accelerator group frees the
670    // closures allocated in ConnectAccelerators.
671    AcceleratorsGtk* accelerators = AcceleratorsGtk::GetInstance();
672    for (AcceleratorsGtk::const_iterator iter = accelerators->begin();
673         iter != accelerators->end(); ++iter) {
674      gtk_accel_group_disconnect_key(accel_group_,
675          iter->second.GetGdkKeyCode(),
676          static_cast<GdkModifierType>(iter->second.modifiers()));
677    }
678    gtk_window_remove_accel_group(window_, accel_group_);
679    g_object_unref(accel_group_);
680    accel_group_ = NULL;
681  }
682
683  // Cancel any pending callback from the window configure debounce timer.
684  window_configure_debounce_timer_.Stop();
685
686  // Likewise for the loading animation.
687  loading_animation_timer_.Stop();
688
689  GtkWidget* window = GTK_WIDGET(window_);
690  // To help catch bugs in any event handlers that might get fired during the
691  // destruction, set window_ to NULL before any handlers will run.
692  window_ = NULL;
693  titlebar_->set_window(NULL);
694  gtk_widget_destroy(window);
695}
696
697void BrowserWindowGtk::Activate() {
698  gtk_window_present(window_);
699}
700
701void BrowserWindowGtk::Deactivate() {
702  gdk_window_lower(GTK_WIDGET(window_)->window);
703}
704
705bool BrowserWindowGtk::IsActive() const {
706  return is_active_;
707}
708
709void BrowserWindowGtk::FlashFrame() {
710  // May not be respected by all window managers.
711  gtk_window_set_urgency_hint(window_, TRUE);
712}
713
714gfx::NativeWindow BrowserWindowGtk::GetNativeHandle() {
715  return window_;
716}
717
718BrowserWindowTesting* BrowserWindowGtk::GetBrowserWindowTesting() {
719  NOTIMPLEMENTED();
720  return NULL;
721}
722
723StatusBubble* BrowserWindowGtk::GetStatusBubble() {
724  return status_bubble_.get();
725}
726
727void BrowserWindowGtk::ToolbarSizeChanged(bool is_animating) {
728  // On Windows, this is used for a performance optimization.
729  // http://code.google.com/p/chromium/issues/detail?id=12291
730}
731
732void BrowserWindowGtk::UpdateTitleBar() {
733  string16 title = browser_->GetWindowTitleForCurrentTab();
734  gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
735  if (ShouldShowWindowIcon())
736    titlebar_->UpdateTitleAndIcon();
737}
738
739void BrowserWindowGtk::ShelfVisibilityChanged() {
740  MaybeShowBookmarkBar(false);
741}
742
743void BrowserWindowGtk::UpdateDevTools() {
744  UpdateDevToolsForContents(
745      browser_->GetSelectedTabContents());
746}
747
748void BrowserWindowGtk::UpdateLoadingAnimations(bool should_animate) {
749  if (should_animate) {
750    if (!loading_animation_timer_.IsRunning()) {
751      // Loads are happening, and the timer isn't running, so start it.
752      loading_animation_timer_.Start(
753          base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), this,
754          &BrowserWindowGtk::LoadingAnimationCallback);
755    }
756  } else {
757    if (loading_animation_timer_.IsRunning()) {
758      loading_animation_timer_.Stop();
759      // Loads are now complete, update the state if a task was scheduled.
760      LoadingAnimationCallback();
761    }
762  }
763}
764
765void BrowserWindowGtk::LoadingAnimationCallback() {
766  if (browser_->type() == Browser::TYPE_NORMAL) {
767    // Loading animations are shown in the tab for tabbed windows.  We check the
768    // browser type instead of calling IsTabStripVisible() because the latter
769    // will return false for fullscreen windows, but we still need to update
770    // their animations (so that when they come out of fullscreen mode they'll
771    // be correct).
772    tabstrip_->UpdateLoadingAnimations();
773  } else if (ShouldShowWindowIcon()) {
774    // ... or in the window icon area for popups and app windows.
775    TabContents* tab_contents = browser_->GetSelectedTabContents();
776    // GetSelectedTabContents can return NULL for example under Purify when
777    // the animations are running slowly and this function is called on
778    // a timer through LoadingAnimationCallback.
779    titlebar_->UpdateThrobber(tab_contents);
780  }
781}
782
783void BrowserWindowGtk::SetStarredState(bool is_starred) {
784  toolbar_->GetLocationBarView()->SetStarred(is_starred);
785}
786
787gfx::Rect BrowserWindowGtk::GetRestoredBounds() const {
788  return restored_bounds_;
789}
790
791gfx::Rect BrowserWindowGtk::GetBounds() const {
792  return bounds_;
793}
794
795bool BrowserWindowGtk::IsMaximized() const {
796  return (state_ & GDK_WINDOW_STATE_MAXIMIZED);
797}
798
799bool BrowserWindowGtk::ShouldDrawContentDropShadow() {
800  return !IsMaximized() && UseCustomFrame();
801}
802
803void BrowserWindowGtk::SetFullscreen(bool fullscreen) {
804  // gtk_window_(un)fullscreen asks the window manager to toggle the EWMH
805  // for fullscreen windows.  Not all window managers support this.
806  if (fullscreen) {
807    gtk_window_fullscreen(window_);
808  } else {
809    // Work around a bug where if we try to unfullscreen, metacity immediately
810    // fullscreens us again.  This is a little flickery and not necessary if
811    // there's a gnome-panel, but it's not easy to detect whether there's a
812    // panel or not.
813    std::string wm_name;
814    bool unmaximize_before_unfullscreen = IsMaximized() &&
815        ui::GetWindowManagerName(&wm_name) && wm_name == "Metacity";
816    if (unmaximize_before_unfullscreen)
817      UnMaximize();
818
819    gtk_window_unfullscreen(window_);
820
821    if (unmaximize_before_unfullscreen)
822      gtk_window_maximize(window_);
823  }
824}
825
826bool BrowserWindowGtk::IsFullscreen() const {
827  return (state_ & GDK_WINDOW_STATE_FULLSCREEN);
828}
829
830bool BrowserWindowGtk::IsFullscreenBubbleVisible() const {
831  return fullscreen_exit_bubble_.get() ? true : false;
832}
833
834LocationBar* BrowserWindowGtk::GetLocationBar() const {
835  return toolbar_->GetLocationBar();
836}
837
838void BrowserWindowGtk::SetFocusToLocationBar(bool select_all) {
839  if (!IsFullscreen())
840    GetLocationBar()->FocusLocation(select_all);
841}
842
843void BrowserWindowGtk::UpdateReloadStopState(bool is_loading, bool force) {
844  toolbar_->GetReloadButton()->ChangeMode(
845      is_loading ? ReloadButtonGtk::MODE_STOP : ReloadButtonGtk::MODE_RELOAD,
846      force);
847}
848
849void BrowserWindowGtk::UpdateToolbar(TabContentsWrapper* contents,
850                                     bool should_restore_state) {
851  toolbar_->UpdateTabContents(contents->tab_contents(), should_restore_state);
852}
853
854void BrowserWindowGtk::FocusToolbar() {
855  NOTIMPLEMENTED();
856}
857
858void BrowserWindowGtk::FocusAppMenu() {
859  NOTIMPLEMENTED();
860}
861
862void BrowserWindowGtk::FocusBookmarksToolbar() {
863  NOTIMPLEMENTED();
864}
865
866void BrowserWindowGtk::FocusChromeOSStatus() {
867  NOTIMPLEMENTED();
868}
869
870void BrowserWindowGtk::RotatePaneFocus(bool forwards) {
871  NOTIMPLEMENTED();
872}
873
874bool BrowserWindowGtk::IsBookmarkBarVisible() const {
875  return (browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR) &&
876          bookmark_bar_.get() &&
877          browser_->profile()->GetPrefs()->GetBoolean(
878              prefs::kShowBookmarkBar) &&
879          browser_->profile()->GetPrefs()->GetBoolean(
880              prefs::kEnableBookmarkBar));
881}
882
883bool BrowserWindowGtk::IsBookmarkBarAnimating() const {
884  if (IsBookmarkBarSupported() && bookmark_bar_->IsAnimating())
885    return true;
886  return false;
887}
888
889bool BrowserWindowGtk::IsTabStripEditable() const {
890  return !tabstrip()->IsDragSessionActive() &&
891      !tabstrip()->IsActiveDropTarget();
892}
893
894bool BrowserWindowGtk::IsToolbarVisible() const {
895  return IsToolbarSupported();
896}
897
898void BrowserWindowGtk::ConfirmAddSearchProvider(const TemplateURL* template_url,
899                                                Profile* profile) {
900  new EditSearchEngineDialog(window_, template_url, NULL, profile);
901}
902
903void BrowserWindowGtk::ToggleBookmarkBar() {
904  bookmark_utils::ToggleWhenVisible(browser_->profile());
905}
906
907void BrowserWindowGtk::ShowAboutChromeDialog() {
908  ShowAboutDialogForProfile(window_, browser_->profile());
909}
910
911void BrowserWindowGtk::ShowUpdateChromeDialog() {
912  UpdateRecommendedDialog::Show(window_);
913}
914
915void BrowserWindowGtk::ShowTaskManager() {
916  TaskManagerGtk::Show(false);
917}
918
919void BrowserWindowGtk::ShowBackgroundPages() {
920  TaskManagerGtk::Show(true);
921}
922
923void BrowserWindowGtk::ShowBookmarkBubble(const GURL& url,
924                                          bool already_bookmarked) {
925  toolbar_->GetLocationBarView()->ShowStarBubble(url, !already_bookmarked);
926}
927
928bool BrowserWindowGtk::IsDownloadShelfVisible() const {
929  return download_shelf_.get() && download_shelf_->IsShowing();
930}
931
932DownloadShelf* BrowserWindowGtk::GetDownloadShelf() {
933  if (!download_shelf_.get())
934    download_shelf_.reset(new DownloadShelfGtk(browser_.get(),
935                                               render_area_vbox_));
936  return download_shelf_.get();
937}
938
939void BrowserWindowGtk::ShowRepostFormWarningDialog(TabContents* tab_contents) {
940  new RepostFormWarningGtk(GetNativeHandle(), tab_contents);
941}
942
943void BrowserWindowGtk::ShowCollectedCookiesDialog(TabContents* tab_contents) {
944  // Deletes itself on close.
945  new CollectedCookiesGtk(GetNativeHandle(), tab_contents);
946}
947
948void BrowserWindowGtk::ShowThemeInstallBubble() {
949  ThemeInstallBubbleViewGtk::Show(window_);
950}
951
952void BrowserWindowGtk::ShowHTMLDialog(HtmlDialogUIDelegate* delegate,
953                                      gfx::NativeWindow parent_window) {
954  browser::ShowHtmlDialog(parent_window, browser_->profile(), delegate);
955}
956
957void BrowserWindowGtk::UserChangedTheme() {
958  SetBackgroundColor();
959  gdk_window_invalidate_rect(GTK_WIDGET(window_)->window,
960      &GTK_WIDGET(window_)->allocation, TRUE);
961  UpdateWindowShape(bounds_.width(), bounds_.height());
962}
963
964int BrowserWindowGtk::GetExtraRenderViewHeight() const {
965  int sum = infobar_container_->TotalHeightOfAnimatingBars();
966  if (IsBookmarkBarSupported() && bookmark_bar_->IsAnimating())
967    sum += bookmark_bar_->GetHeight();
968  if (download_shelf_.get() && download_shelf_->IsClosing())
969    sum += download_shelf_->GetHeight();
970  return sum;
971}
972
973void BrowserWindowGtk::TabContentsFocused(TabContents* tab_contents) {
974  NOTIMPLEMENTED();
975}
976
977void BrowserWindowGtk::ShowPageInfo(Profile* profile,
978                                    const GURL& url,
979                                    const NavigationEntry::SSLStatus& ssl,
980                                    bool show_history) {
981  browser::ShowPageInfoBubble(window_, profile, url, ssl, show_history);
982}
983
984void BrowserWindowGtk::ShowAppMenu() {
985  toolbar_->ShowAppMenu();
986}
987
988bool BrowserWindowGtk::PreHandleKeyboardEvent(
989    const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
990  GdkEventKey* os_event = event.os_event;
991
992  if (!os_event || event.type != WebKit::WebInputEvent::RawKeyDown)
993    return false;
994
995  // We first find out the browser command associated to the |event|.
996  // Then if the command is a reserved one, and should be processed immediately
997  // according to the |event|, the command will be executed immediately.
998  // Otherwise we just set |*is_keyboard_shortcut| properly and return false.
999
1000  // First check if it's a custom accelerator.
1001  int id = GetCustomCommandId(os_event);
1002
1003  // Then check if it's a predefined accelerator bound to the window.
1004  if (id == -1) {
1005    // This piece of code is based on the fact that calling
1006    // gtk_window_activate_key() method against |window_| may only trigger a
1007    // browser command execution, by matching a global accelerator
1008    // defined in above |kAcceleratorMap|.
1009    //
1010    // Here we need to retrieve the command id (if any) associated to the
1011    // keyboard event. Instead of looking up the command id in above
1012    // |kAcceleratorMap| table by ourselves, we block the command execution of
1013    // the |browser_| object then send the keyboard event to the |window_| by
1014    // calling gtk_window_activate_key() method, as if we are activating an
1015    // accelerator key. Then we can retrieve the command id from the
1016    // |browser_| object.
1017    //
1018    // Pros of this approach:
1019    // 1. We don't need to care about keyboard layout problem, as
1020    //    gtk_window_activate_key() method handles it for us.
1021    //
1022    // Cons:
1023    // 1. The logic is a little complicated.
1024    // 2. We should be careful not to introduce any accelerators that trigger
1025    //    customized code instead of browser commands.
1026    browser_->SetBlockCommandExecution(true);
1027    gtk_window_activate_key(window_, os_event);
1028    // We don't need to care about the WindowOpenDisposition value,
1029    // because all commands executed in this path use the default value.
1030    id = browser_->GetLastBlockedCommand(NULL);
1031    browser_->SetBlockCommandExecution(false);
1032  }
1033
1034  if (id == -1)
1035    return false;
1036
1037  // Executing the command may cause |this| object to be destroyed.
1038  if (browser_->IsReservedCommandOrKey(id, event) && !event.match_edit_command)
1039    return browser_->ExecuteCommandIfEnabled(id);
1040
1041  // The |event| is a keyboard shortcut.
1042  DCHECK(is_keyboard_shortcut != NULL);
1043  *is_keyboard_shortcut = true;
1044
1045  return false;
1046}
1047
1048void BrowserWindowGtk::HandleKeyboardEvent(
1049    const NativeWebKeyboardEvent& event) {
1050  GdkEventKey* os_event = event.os_event;
1051
1052  if (!os_event || event.type != WebKit::WebInputEvent::RawKeyDown)
1053    return;
1054
1055  // Handles a key event in following sequence:
1056  // 1. Our special key accelerators, such as ctrl-tab, etc.
1057  // 2. Gtk accelerators.
1058  // This sequence matches the default key press handler of GtkWindow.
1059  //
1060  // It's not necessary to care about the keyboard layout, as
1061  // gtk_window_activate_key() takes care of it automatically.
1062  int id = GetCustomCommandId(os_event);
1063  if (id != -1)
1064    browser_->ExecuteCommandIfEnabled(id);
1065  else
1066    gtk_window_activate_key(window_, os_event);
1067}
1068
1069void BrowserWindowGtk::ShowCreateWebAppShortcutsDialog(
1070    TabContentsWrapper* tab_contents) {
1071  CreateWebApplicationShortcutsDialogGtk::Show(window_, tab_contents);
1072}
1073
1074void BrowserWindowGtk::ShowCreateChromeAppShortcutsDialog(
1075    Profile* profile, const Extension* app) {
1076  CreateChromeApplicationShortcutsDialogGtk::Show(window_, app);
1077}
1078
1079void BrowserWindowGtk::Cut() {
1080  gtk_util::DoCut(this);
1081}
1082
1083void BrowserWindowGtk::Copy() {
1084  gtk_util::DoCopy(this);
1085}
1086
1087void BrowserWindowGtk::Paste() {
1088  gtk_util::DoPaste(this);
1089}
1090
1091void BrowserWindowGtk::PrepareForInstant() {
1092  TabContentsWrapper* contents = contents_container_->tab();
1093  if (contents)
1094    FadeForInstant(true);
1095}
1096
1097void BrowserWindowGtk::ShowInstant(TabContentsWrapper* preview) {
1098  contents_container_->SetPreview(preview);
1099  MaybeShowBookmarkBar(false);
1100
1101  TabContentsWrapper* contents = contents_container_->tab();
1102  if (contents)
1103    CancelInstantFade();
1104}
1105
1106void BrowserWindowGtk::HideInstant(bool instant_is_active) {
1107  contents_container_->PopPreview();
1108  MaybeShowBookmarkBar(false);
1109
1110  TabContentsWrapper* contents = contents_container_->tab();
1111  if (contents) {
1112    if (instant_is_active)
1113      FadeForInstant(false);
1114    else
1115      CancelInstantFade();
1116  }
1117}
1118
1119gfx::Rect BrowserWindowGtk::GetInstantBounds() {
1120  return gtk_util::GetWidgetScreenBounds(contents_container_->widget());
1121}
1122
1123void BrowserWindowGtk::ConfirmBrowserCloseWithPendingDownloads() {
1124  new DownloadInProgressDialogGtk(browser());
1125}
1126
1127void BrowserWindowGtk::Observe(NotificationType type,
1128                               const NotificationSource& source,
1129                               const NotificationDetails& details) {
1130  switch (type.value) {
1131    case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED:
1132      MaybeShowBookmarkBar(true);
1133      break;
1134
1135    case NotificationType::PREF_CHANGED: {
1136      std::string* pref_name = Details<std::string>(details).ptr();
1137      if (*pref_name == prefs::kUseCustomChromeFrame) {
1138        UpdateCustomFrame();
1139      } else {
1140        NOTREACHED() << "Got pref change notification we didn't register for!";
1141      }
1142      break;
1143    }
1144
1145    default:
1146      NOTREACHED() << "Got a notification we didn't register for!";
1147  }
1148}
1149
1150void BrowserWindowGtk::TabDetachedAt(TabContentsWrapper* contents, int index) {
1151  // We use index here rather than comparing |contents| because by this time
1152  // the model has already removed |contents| from its list, so
1153  // browser_->GetSelectedTabContents() will return NULL or something else.
1154  if (index == browser_->tabstrip_model()->active_index()) {
1155    infobar_container_->ChangeTabContents(NULL);
1156    UpdateDevToolsForContents(NULL);
1157  }
1158  contents_container_->DetachTab(contents);
1159}
1160
1161void BrowserWindowGtk::TabSelectedAt(TabContentsWrapper* old_contents,
1162                                     TabContentsWrapper* new_contents,
1163                                     int index,
1164                                     bool user_gesture) {
1165  if (old_contents == new_contents)
1166    return;
1167
1168  if (old_contents && !old_contents->tab_contents()->is_being_destroyed())
1169    old_contents->view()->StoreFocus();
1170
1171  // Update various elements that are interested in knowing the current
1172  // TabContents.
1173  infobar_container_->ChangeTabContents(new_contents->tab_contents());
1174  contents_container_->SetTab(new_contents);
1175  UpdateDevToolsForContents(new_contents->tab_contents());
1176
1177  new_contents->tab_contents()->DidBecomeSelected();
1178  // TODO(estade): after we manage browser activation, add a check to make sure
1179  // we are the active browser before calling RestoreFocus().
1180  if (!browser_->tabstrip_model()->closing_all()) {
1181    new_contents->view()->RestoreFocus();
1182    if (new_contents->find_tab_helper()->find_ui_active())
1183      browser_->GetFindBarController()->find_bar()->SetFocusAndSelection();
1184  }
1185
1186  // Update all the UI bits.
1187  UpdateTitleBar();
1188  UpdateToolbar(new_contents, true);
1189  MaybeShowBookmarkBar(false);
1190}
1191
1192void BrowserWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
1193  // Do nothing if we're in the process of closing the browser window.
1194  if (!window_)
1195    return;
1196
1197  bool is_active = (GTK_WIDGET(window_)->window == active_window);
1198  bool changed = (is_active != is_active_);
1199
1200  if (is_active && changed) {
1201    // If there's an app modal dialog (e.g., JS alert), try to redirect
1202    // the user's attention to the window owning the dialog.
1203    if (AppModalDialogQueue::GetInstance()->HasActiveDialog()) {
1204      AppModalDialogQueue::GetInstance()->ActivateModalDialog();
1205      return;
1206    }
1207  }
1208
1209  is_active_ = is_active;
1210  if (changed) {
1211    SetBackgroundColor();
1212    gdk_window_invalidate_rect(GTK_WIDGET(window_)->window,
1213                               &GTK_WIDGET(window_)->allocation, TRUE);
1214    // For some reason, the above two calls cause the window shape to be
1215    // lost so reset it.
1216    UpdateWindowShape(bounds_.width(), bounds_.height());
1217  }
1218}
1219
1220void BrowserWindowGtk::FadeForInstant(bool animate) {
1221  DCHECK(contents_container_->tab());
1222  RenderWidgetHostView* rwhv =
1223      contents_container_->tab()->tab_contents()->GetRenderWidgetHostView();
1224  if (rwhv) {
1225    SkColor whitish = SkColorSetARGB(192, 255, 255, 255);
1226    rwhv->SetVisuallyDeemphasized(&whitish, animate);
1227  }
1228}
1229
1230void BrowserWindowGtk::CancelInstantFade() {
1231  DCHECK(contents_container_->tab());
1232  RenderWidgetHostView* rwhv =
1233      contents_container_->tab()->tab_contents()->GetRenderWidgetHostView();
1234  if (rwhv)
1235    rwhv->SetVisuallyDeemphasized(NULL, false);
1236}
1237
1238void BrowserWindowGtk::MaybeShowBookmarkBar(bool animate) {
1239  if (!IsBookmarkBarSupported())
1240    return;
1241
1242  TabContents* contents = GetDisplayedTabContents();
1243  bool show_bar = false;
1244
1245  if (contents) {
1246    bookmark_bar_->SetProfile(contents->profile());
1247    bookmark_bar_->SetPageNavigator(contents);
1248    show_bar = true;
1249  }
1250
1251  if (show_bar && contents && !contents->ShouldShowBookmarkBar()) {
1252    PrefService* prefs = contents->profile()->GetPrefs();
1253    show_bar = prefs->GetBoolean(prefs::kShowBookmarkBar) &&
1254               prefs->GetBoolean(prefs::kEnableBookmarkBar) &&
1255               !IsFullscreen();
1256  }
1257
1258  if (show_bar) {
1259    bookmark_bar_->Show(animate);
1260  } else if (IsFullscreen()) {
1261    bookmark_bar_->EnterFullscreen();
1262  } else {
1263    bookmark_bar_->Hide(animate);
1264  }
1265}
1266
1267void BrowserWindowGtk::UpdateDevToolsForContents(TabContents* contents) {
1268  TabContentsWrapper* old_devtools = devtools_container_->tab();
1269  TabContentsWrapper* devtools_contents = contents ?
1270      DevToolsWindow::GetDevToolsContents(contents) : NULL;
1271  if (old_devtools == devtools_contents)
1272    return;
1273
1274  if (old_devtools)
1275    devtools_container_->DetachTab(old_devtools);
1276
1277  devtools_container_->SetTab(devtools_contents);
1278  if (devtools_contents) {
1279    // TabContentsViewGtk::WasShown is not called when tab contents is shown by
1280    // anything other than user selecting a Tab.
1281    // See TabContentsViewViews::OnWindowPosChanged for reference on how it
1282    // should be implemented.
1283    devtools_contents->tab_contents()->ShowContents();
1284  }
1285
1286  bool should_show = old_devtools == NULL && devtools_contents != NULL;
1287  bool should_hide = old_devtools != NULL && devtools_contents == NULL;
1288  if (should_show) {
1289    gtk_widget_show(devtools_container_->widget());
1290  } else if (should_hide) {
1291    // Store split offset when hiding devtools window only.
1292    gint divider_offset = gtk_paned_get_position(GTK_PANED(contents_split_));
1293    browser_->profile()->GetPrefs()->
1294        SetInteger(prefs::kDevToolsSplitLocation, divider_offset);
1295    gtk_widget_hide(devtools_container_->widget());
1296  }
1297}
1298
1299void BrowserWindowGtk::DestroyBrowser() {
1300  browser_.reset();
1301}
1302
1303gboolean BrowserWindowGtk::OnConfigure(GtkWidget* widget,
1304                                       GdkEventConfigure* event) {
1305  gfx::Rect bounds(event->x, event->y, event->width, event->height);
1306
1307  // When the window moves, we'll get multiple configure-event signals. We can
1308  // also get events when the bounds haven't changed, but the window's stacking
1309  // has, which we aren't interested in. http://crbug.com/70125
1310  if (bounds == bounds_)
1311    return FALSE;
1312
1313  GetLocationBar()->location_entry()->ClosePopup();
1314
1315  TabContents* tab_contents = GetDisplayedTabContents();
1316  if (tab_contents)
1317    tab_contents->WindowMoveOrResizeStarted();
1318
1319  if (bounds_.size() != bounds.size())
1320    OnSizeChanged(bounds.width(), bounds.height());
1321
1322  // We update |bounds_| but not |restored_bounds_| here.  The latter needs
1323  // to be updated conditionally when the window is non-maximized and non-
1324  // fullscreen, but whether those state updates have been processed yet is
1325  // window-manager specific.  We update |restored_bounds_| in the debounced
1326  // handler below, after the window state has been updated.
1327  bounds_ = bounds;
1328
1329  // The GdkEventConfigure* we get here doesn't have quite the right
1330  // coordinates though (they're relative to the drawable window area, rather
1331  // than any window manager decorations, if enabled), so we need to call
1332  // gtk_window_get_position() to get the right values.  (Otherwise session
1333  // restore, if enabled, will restore windows to incorrect positions.)  That's
1334  // a round trip to the X server though, so we set a debounce timer and only
1335  // call it (in OnDebouncedBoundsChanged() below) after we haven't seen a
1336  // reconfigure event in a short while.
1337  // We don't use Reset() because the timer may not yet be running.
1338  // (In that case Stop() is a no-op.)
1339  if (!debounce_timer_disabled_) {
1340    window_configure_debounce_timer_.Stop();
1341    window_configure_debounce_timer_.Start(base::TimeDelta::FromMilliseconds(
1342        kDebounceTimeoutMilliseconds), this,
1343        &BrowserWindowGtk::OnDebouncedBoundsChanged);
1344  }
1345
1346  return FALSE;
1347}
1348
1349void BrowserWindowGtk::OnDebouncedBoundsChanged() {
1350  gint x, y;
1351  gtk_window_get_position(window_, &x, &y);
1352  gfx::Point origin(x, y);
1353  bounds_.set_origin(origin);
1354  if (!IsFullscreen() && !IsMaximized())
1355    restored_bounds_ = bounds_;
1356  SaveWindowPosition();
1357}
1358
1359gboolean BrowserWindowGtk::OnWindowState(GtkWidget* sender,
1360                                         GdkEventWindowState* event) {
1361  state_ = event->new_window_state;
1362
1363  if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1364    bool is_fullscreen = state_ & GDK_WINDOW_STATE_FULLSCREEN;
1365    browser_->UpdateCommandsForFullscreenMode(is_fullscreen);
1366    if (is_fullscreen) {
1367      UpdateCustomFrame();
1368      toolbar_->Hide();
1369      tabstrip_->Hide();
1370      if (IsBookmarkBarSupported())
1371        bookmark_bar_->EnterFullscreen();
1372      bool is_kiosk =
1373          CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
1374      if (!is_kiosk) {
1375        fullscreen_exit_bubble_.reset(new FullscreenExitBubbleGtk(
1376            GTK_FLOATING_CONTAINER(render_area_floating_container_)));
1377      }
1378      gtk_widget_hide(toolbar_border_);
1379    } else {
1380      fullscreen_exit_bubble_.reset();
1381      UpdateCustomFrame();
1382      ShowSupportedWindowFeatures();
1383    }
1384  }
1385
1386  UpdateWindowShape(bounds_.width(), bounds_.height());
1387  SaveWindowPosition();
1388  return FALSE;
1389}
1390
1391// Callback for the delete event.  This event is fired when the user tries to
1392// close the window (e.g., clicking on the X in the window manager title bar).
1393gboolean BrowserWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget,
1394                                                   GdkEvent* event) {
1395  Close();
1396
1397  // Return true to prevent the gtk window from being destroyed.  Close will
1398  // destroy it for us.
1399  return TRUE;
1400}
1401
1402void BrowserWindowGtk::OnMainWindowDestroy(GtkWidget* widget) {
1403  // BUG 8712. When we gtk_widget_destroy() in Close(), this will emit the
1404  // signal right away, and we will be here (while Close() is still in the
1405  // call stack).  In order to not reenter Close(), and to also follow the
1406  // expectations of BrowserList, we should run the BrowserWindowGtk destructor
1407  // not now, but after the run loop goes back to process messages.  Otherwise
1408  // we will remove ourself from BrowserList while it's being iterated.
1409  // Additionally, now that we know the window is gone, we need to make sure to
1410  // set window_ to NULL, otherwise we will try to close the window again when
1411  // we call Close() in the destructor.
1412  //
1413  // We don't want to use DeleteSoon() here since it won't work on a nested pump
1414  // (like in UI tests).
1415  MessageLoop::current()->PostTask(FROM_HERE,
1416                                   new DeleteTask<BrowserWindowGtk>(this));
1417}
1418
1419void BrowserWindowGtk::UnMaximize() {
1420  gtk_window_unmaximize(window_);
1421
1422  // It can happen that you end up with a window whose restore size is the same
1423  // as the size of the screen, so unmaximizing it merely remaximizes it due to
1424  // the same WM feature that SetWindowSize() works around.  We try to detect
1425  // this and resize the window to work around the issue.
1426  if (bounds_.size() == restored_bounds_.size())
1427    gtk_window_resize(window_, bounds_.width(), bounds_.height() - 1);
1428}
1429
1430bool BrowserWindowGtk::CanClose() const {
1431  // You cannot close a frame for which there is an active originating drag
1432  // session.
1433  if (tabstrip_->IsDragSessionActive())
1434    return false;
1435
1436  // Give beforeunload handlers the chance to cancel the close before we hide
1437  // the window below.
1438  if (!browser_->ShouldCloseWindow())
1439    return false;
1440
1441  if (!browser_->tabstrip_model()->empty()) {
1442    // Tab strip isn't empty.  Hide the window (so it appears to have closed
1443    // immediately) and close all the tabs, allowing the renderers to shut
1444    // down. When the tab strip is empty we'll be called back again.
1445    gtk_widget_hide(GTK_WIDGET(window_));
1446    browser_->OnWindowClosing();
1447    return false;
1448  }
1449
1450  // Empty TabStripModel, it's now safe to allow the Window to be closed.
1451  NotificationService::current()->Notify(
1452      NotificationType::WINDOW_CLOSED,
1453      Source<GtkWindow>(window_),
1454      NotificationService::NoDetails());
1455  return true;
1456}
1457
1458bool BrowserWindowGtk::ShouldShowWindowIcon() const {
1459  return browser_->SupportsWindowFeature(Browser::FEATURE_TITLEBAR);
1460}
1461
1462void BrowserWindowGtk::DisableDebounceTimerForTests(bool is_disabled) {
1463  debounce_timer_disabled_ = is_disabled;
1464  if (is_disabled)
1465    window_configure_debounce_timer_.Stop();
1466}
1467
1468void BrowserWindowGtk::AddFindBar(FindBarGtk* findbar) {
1469  gtk_floating_container_add_floating(
1470      GTK_FLOATING_CONTAINER(render_area_floating_container_),
1471      findbar->widget());
1472}
1473
1474void BrowserWindowGtk::ResetCustomFrameCursor() {
1475  if (!frame_cursor_)
1476    return;
1477
1478  frame_cursor_ = NULL;
1479  gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL);
1480}
1481
1482// static
1483BrowserWindowGtk* BrowserWindowGtk::GetBrowserWindowForNativeWindow(
1484    gfx::NativeWindow window) {
1485  if (window) {
1486    return static_cast<BrowserWindowGtk*>(
1487        g_object_get_qdata(G_OBJECT(window), GetBrowserWindowQuarkKey()));
1488  }
1489
1490  return NULL;
1491}
1492
1493// static
1494GtkWindow* BrowserWindowGtk::GetBrowserWindowForXID(XID xid) {
1495  std::map<XID, GtkWindow*>::iterator iter =
1496      BrowserWindowGtk::xid_map_.find(xid);
1497  return (iter != BrowserWindowGtk::xid_map_.end()) ? iter->second : NULL;
1498}
1499
1500// static
1501void BrowserWindowGtk::RegisterUserPrefs(PrefService* prefs) {
1502  bool custom_frame_default = false;
1503  // Avoid checking the window manager if we're not connected to an X server (as
1504  // is the case in Valgrind tests).
1505  if (ui::XDisplayExists() &&
1506      !prefs->HasPrefPath(prefs::kUseCustomChromeFrame)) {
1507    custom_frame_default = GetCustomFramePrefDefault();
1508  }
1509  prefs->RegisterBooleanPref(
1510      prefs::kUseCustomChromeFrame, custom_frame_default);
1511}
1512
1513void BrowserWindowGtk::BookmarkBarIsFloating(bool is_floating) {
1514  bookmark_bar_is_floating_ = is_floating;
1515  toolbar_->UpdateForBookmarkBarVisibility(is_floating);
1516
1517  // This can be NULL during initialization of the bookmark bar.
1518  if (bookmark_bar_.get())
1519    PlaceBookmarkBar(is_floating);
1520}
1521
1522TabContents* BrowserWindowGtk::GetDisplayedTabContents() {
1523  return contents_container_->GetVisibleTabContents();
1524}
1525
1526void BrowserWindowGtk::QueueToolbarRedraw() {
1527  gtk_widget_queue_draw(toolbar_->widget());
1528}
1529
1530void BrowserWindowGtk::SetGeometryHints() {
1531  // If we call gtk_window_maximize followed by gtk_window_present, compiz gets
1532  // confused and maximizes the window, but doesn't set the
1533  // GDK_WINDOW_STATE_MAXIMIZED bit.  So instead, we keep track of whether to
1534  // maximize and call it after gtk_window_present.
1535  maximize_after_show_ = browser_->GetSavedMaximizedState();
1536
1537  gfx::Rect bounds = browser_->GetSavedWindowBounds();
1538  // We don't blindly call SetBounds here: that sets a forced position
1539  // on the window and we intentionally *don't* do that for normal
1540  // windows.  Most programs do not restore their window position on
1541  // Linux, instead letting the window manager choose a position.
1542  //
1543  // However, in cases like dropping a tab where the bounds are
1544  // specifically set, we do want to position explicitly.  We also
1545  // force the position as part of session restore, as applications
1546  // that restore other, similar state (for instance GIMP, audacity,
1547  // pidgin, dia, and gkrellm) do tend to restore their positions.
1548  //
1549  // For popup windows, we assume that if x == y == 0, the opening page
1550  // did not specify a position.  Let the WM position the popup instead.
1551  bool is_popup = browser_->type() & Browser::TYPE_POPUP;
1552  bool popup_without_position = is_popup &&
1553      bounds.x() == 0 && bounds.y() == 0;
1554  bool move = browser_->bounds_overridden() && !popup_without_position;
1555  SetBoundsImpl(bounds, !is_popup, move);
1556}
1557
1558void BrowserWindowGtk::ConnectHandlersToSignals() {
1559  g_signal_connect(window_, "delete-event",
1560                   G_CALLBACK(OnMainWindowDeleteEventThunk), this);
1561  g_signal_connect(window_, "destroy",
1562                   G_CALLBACK(OnMainWindowDestroyThunk), this);
1563  g_signal_connect(window_, "configure-event",
1564                   G_CALLBACK(OnConfigureThunk), this);
1565  g_signal_connect(window_, "window-state-event",
1566                   G_CALLBACK(OnWindowStateThunk), this);
1567  g_signal_connect(window_, "map",
1568                   G_CALLBACK(MainWindowMapped), NULL);
1569  g_signal_connect(window_, "unmap",
1570                   G_CALLBACK(MainWindowUnMapped), NULL);
1571  g_signal_connect(window_, "key-press-event",
1572                   G_CALLBACK(OnKeyPressThunk), this);
1573  g_signal_connect(window_, "motion-notify-event",
1574                   G_CALLBACK(OnMouseMoveEventThunk), this);
1575  g_signal_connect(window_, "button-press-event",
1576                   G_CALLBACK(OnButtonPressEventThunk), this);
1577  g_signal_connect(window_, "focus-in-event",
1578                   G_CALLBACK(OnFocusInThunk), this);
1579  g_signal_connect(window_, "focus-out-event",
1580                   G_CALLBACK(OnFocusOutThunk), this);
1581}
1582
1583void BrowserWindowGtk::InitWidgets() {
1584  ConnectHandlersToSignals();
1585  bounds_ = restored_bounds_ = GetInitialWindowBounds(window_);
1586
1587  // This vbox encompasses all of the widgets within the browser.  This is
1588  // everything except the custom frame border.
1589  window_vbox_ = gtk_vbox_new(FALSE, 0);
1590  gtk_widget_show(window_vbox_);
1591
1592  // We hold an always hidden GtkMenuBar inside our browser window simply to
1593  // fool the Unity desktop, which will mirror the contents of the first
1594  // GtkMenuBar it sees into the global menu bar. (It doesn't seem to check the
1595  // visibility of the GtkMenuBar, so we can just permanently hide it.)
1596  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGlobalGnomeMenu)) {
1597    global_menu_bar_.reset(new GlobalMenuBar(browser_.get(), this));
1598    gtk_container_add(GTK_CONTAINER(window_vbox_), global_menu_bar_->widget());
1599  }
1600
1601  // The window container draws the custom browser frame.
1602  window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
1603  gtk_widget_set_name(window_container_, "chrome-custom-frame-border");
1604  gtk_widget_set_app_paintable(window_container_, TRUE);
1605  gtk_widget_set_double_buffered(window_container_, FALSE);
1606  gtk_widget_set_redraw_on_allocate(window_container_, TRUE);
1607  g_signal_connect(window_container_, "expose-event",
1608                   G_CALLBACK(OnCustomFrameExposeThunk), this);
1609  gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_);
1610
1611  tabstrip_.reset(new TabStripGtk(browser_->tabstrip_model(), this));
1612  tabstrip_->Init();
1613
1614  // Build the titlebar (tabstrip + header space + min/max/close buttons).
1615  titlebar_.reset(new BrowserTitlebar(this, window_));
1616
1617  // Insert the tabstrip into the window.
1618  gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE,
1619                     0);
1620
1621  toolbar_.reset(new BrowserToolbarGtk(browser_.get(), this));
1622  toolbar_->Init(browser_->profile(), window_);
1623  gtk_box_pack_start(GTK_BOX(window_vbox_), toolbar_->widget(),
1624                     FALSE, FALSE, 0);
1625  g_signal_connect_after(toolbar_->widget(), "expose-event",
1626                         G_CALLBACK(OnExposeDrawInfobarBitsThunk), this);
1627  // This vbox surrounds the render area: find bar, info bars and render view.
1628  // The reason is that this area as a whole needs to be grouped in its own
1629  // GdkWindow hierarchy so that animations originating inside it (infobar,
1630  // download shelf, find bar) are all clipped to that area. This is why
1631  // |render_area_vbox_| is packed in |render_area_event_box_|.
1632  render_area_vbox_ = gtk_vbox_new(FALSE, 0);
1633  gtk_widget_set_name(render_area_vbox_, "chrome-render-area-vbox");
1634  render_area_floating_container_ = gtk_floating_container_new();
1635  gtk_container_add(GTK_CONTAINER(render_area_floating_container_),
1636                    render_area_vbox_);
1637
1638  GtkWidget* location_icon = toolbar_->GetLocationBarView()->
1639      location_icon_widget();
1640  g_signal_connect(location_icon, "size-allocate",
1641                   G_CALLBACK(OnLocationIconSizeAllocateThunk), this);
1642  g_signal_connect_after(location_icon, "expose-event",
1643                         G_CALLBACK(OnExposeDrawInfobarBitsThunk), this);
1644
1645  toolbar_border_ = gtk_event_box_new();
1646  gtk_box_pack_start(GTK_BOX(render_area_vbox_),
1647                     toolbar_border_, FALSE, FALSE, 0);
1648  gtk_widget_set_size_request(toolbar_border_, -1, 1);
1649  gtk_widget_set_no_show_all(toolbar_border_, TRUE);
1650  g_signal_connect_after(toolbar_border_, "expose-event",
1651                         G_CALLBACK(OnExposeDrawInfobarBitsThunk), this);
1652
1653  if (IsToolbarSupported())
1654    gtk_widget_show(toolbar_border_);
1655
1656  infobar_container_.reset(new InfoBarContainerGtk(browser_->profile()));
1657  gtk_box_pack_start(GTK_BOX(render_area_vbox_),
1658                     infobar_container_->widget(),
1659                     FALSE, FALSE, 0);
1660
1661  status_bubble_.reset(new StatusBubbleGtk(browser_->profile()));
1662
1663  contents_container_.reset(new TabContentsContainerGtk(status_bubble_.get()));
1664  devtools_container_.reset(new TabContentsContainerGtk(NULL));
1665  ViewIDUtil::SetID(devtools_container_->widget(), VIEW_ID_DEV_TOOLS_DOCKED);
1666  contents_split_ = gtk_vpaned_new();
1667  gtk_paned_pack1(GTK_PANED(contents_split_), contents_container_->widget(),
1668                  TRUE, TRUE);
1669  gtk_paned_pack2(GTK_PANED(contents_split_), devtools_container_->widget(),
1670                  FALSE, TRUE);
1671  gtk_box_pack_end(GTK_BOX(render_area_vbox_), contents_split_, TRUE, TRUE, 0);
1672  // Restore split offset.
1673  int split_offset = browser_->profile()->GetPrefs()->
1674      GetInteger(prefs::kDevToolsSplitLocation);
1675  if (split_offset != -1) {
1676    if (split_offset < kMinDevToolsHeight)
1677      split_offset = kMinDevToolsHeight;
1678    gtk_paned_set_position(GTK_PANED(contents_split_), split_offset);
1679  } else {
1680    gtk_widget_set_size_request(devtools_container_->widget(), -1,
1681                                kDefaultDevToolsHeight);
1682  }
1683  gtk_widget_show_all(render_area_floating_container_);
1684  gtk_widget_hide(devtools_container_->widget());
1685  render_area_event_box_ = gtk_event_box_new();
1686  // Set a white background so during startup the user sees white in the
1687  // content area before we get a TabContents in place.
1688  gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL,
1689                       &gtk_util::kGdkWhite);
1690  gtk_container_add(GTK_CONTAINER(render_area_event_box_),
1691                    render_area_floating_container_);
1692  gtk_widget_show(render_area_event_box_);
1693  gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_,
1694                   TRUE, TRUE, 0);
1695
1696  if (IsBookmarkBarSupported()) {
1697    bookmark_bar_.reset(new BookmarkBarGtk(this,
1698                                           browser_->profile(),
1699                                           browser_.get(),
1700                                           tabstrip_.get()));
1701    PlaceBookmarkBar(false);
1702    gtk_widget_show(bookmark_bar_->widget());
1703
1704    g_signal_connect_after(bookmark_bar_->widget(), "expose-event",
1705                           G_CALLBACK(OnBookmarkBarExposeThunk), this);
1706    g_signal_connect(bookmark_bar_->widget(), "size-allocate",
1707                     G_CALLBACK(OnBookmarkBarSizeAllocateThunk), this);
1708  }
1709
1710  // We have to realize the window before we try to apply a window shape mask.
1711  gtk_widget_realize(GTK_WIDGET(window_));
1712  state_ = gdk_window_get_state(GTK_WIDGET(window_)->window);
1713  // Note that calling this the first time is necessary to get the
1714  // proper control layout.
1715  UpdateCustomFrame();
1716
1717  // We have to call this after the first window is created, but after that only
1718  // when the theme changes.
1719  static bool default_icon_set = false;
1720  if (!default_icon_set) {
1721    gtk_util::SetDefaultWindowIcon(window_);
1722    default_icon_set = true;
1723  }
1724
1725  gtk_container_add(GTK_CONTAINER(window_), window_container_);
1726  gtk_widget_show(window_container_);
1727  browser_->tabstrip_model()->AddObserver(this);
1728}
1729
1730void BrowserWindowGtk::SetBackgroundColor() {
1731  Profile* profile = browser()->profile();
1732  GtkThemeService* theme_provider = GtkThemeService::GetFrom(profile);
1733  int frame_color_id;
1734  if (UsingCustomPopupFrame()) {
1735    frame_color_id = ThemeService::COLOR_TOOLBAR;
1736  } else if (IsActive()) {
1737    frame_color_id = browser()->profile()->IsOffTheRecord()
1738       ? ThemeService::COLOR_FRAME_INCOGNITO
1739       : ThemeService::COLOR_FRAME;
1740  } else {
1741    frame_color_id = browser()->profile()->IsOffTheRecord()
1742       ? ThemeService::COLOR_FRAME_INCOGNITO_INACTIVE
1743       : ThemeService::COLOR_FRAME_INACTIVE;
1744  }
1745
1746  SkColor frame_color = theme_provider->GetColor(frame_color_id);
1747
1748  // Paint the frame color on the left, right and bottom.
1749  GdkColor frame_color_gdk = gfx::SkColorToGdkColor(frame_color);
1750  gtk_widget_modify_bg(GTK_WIDGET(window_), GTK_STATE_NORMAL,
1751                       &frame_color_gdk);
1752
1753  // Set the color of the dev tools divider.
1754  gtk_widget_modify_bg(contents_split_, GTK_STATE_NORMAL, &frame_color_gdk);
1755
1756  // When the cursor is over the divider, GTK+ normally lightens the background
1757  // color by 1.3 (see LIGHTNESS_MULT in gtkstyle.c).  Since we're setting the
1758  // color, override the prelight also.
1759  color_utils::HSL hsl = { -1, 0.5, 0.65 };
1760  SkColor frame_prelight_color = color_utils::HSLShift(frame_color, hsl);
1761  GdkColor frame_prelight_color_gdk =
1762      gfx::SkColorToGdkColor(frame_prelight_color);
1763  gtk_widget_modify_bg(contents_split_, GTK_STATE_PRELIGHT,
1764      &frame_prelight_color_gdk);
1765
1766  GdkColor border_color = theme_provider->GetBorderColor();
1767  gtk_widget_modify_bg(toolbar_border_, GTK_STATE_NORMAL, &border_color);
1768}
1769
1770void BrowserWindowGtk::OnSizeChanged(int width, int height) {
1771  UpdateWindowShape(width, height);
1772}
1773
1774void BrowserWindowGtk::UpdateWindowShape(int width, int height) {
1775  if (UseCustomFrame() && !IsFullscreen() && !IsMaximized()) {
1776    // Make the corners rounded.  We set a mask that includes most of the
1777    // window except for a few pixels in each corner.
1778    GdkRectangle top_top_rect = { 3, 0, width - 6, 1 };
1779    GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 };
1780    GdkRectangle mid_rect = { 0, 3, width, height - 6 };
1781    // The bottom two rects are mirror images of the top two rects.
1782    GdkRectangle bot_mid_rect = top_mid_rect;
1783    bot_mid_rect.y = height - 3;
1784    GdkRectangle bot_bot_rect = top_top_rect;
1785    bot_bot_rect.y = height - 1;
1786    GdkRegion* mask = gdk_region_rectangle(&top_top_rect);
1787    gdk_region_union_with_rect(mask, &top_mid_rect);
1788    gdk_region_union_with_rect(mask, &mid_rect);
1789    gdk_region_union_with_rect(mask, &bot_mid_rect);
1790    gdk_region_union_with_rect(mask, &bot_bot_rect);
1791    gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, mask, 0, 0);
1792    gdk_region_destroy(mask);
1793    gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1,
1794        kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness);
1795  } else {
1796    // XFCE disables the system decorations if there's an xshape set.
1797    if (UseCustomFrame()) {
1798      // Disable rounded corners.  Simply passing in a NULL region doesn't
1799      // seem to work on KWin, so manually set the shape to the whole window.
1800      GdkRectangle rect = { 0, 0, width, height };
1801      GdkRegion* mask = gdk_region_rectangle(&rect);
1802      gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, mask, 0, 0);
1803      gdk_region_destroy(mask);
1804    } else {
1805      gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, NULL, 0, 0);
1806    }
1807    gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 0, 0, 0, 0);
1808  }
1809}
1810
1811void BrowserWindowGtk::ConnectAccelerators() {
1812  accel_group_ = gtk_accel_group_new();
1813  gtk_window_add_accel_group(window_, accel_group_);
1814
1815  AcceleratorsGtk* accelerators = AcceleratorsGtk::GetInstance();
1816  for (AcceleratorsGtk::const_iterator iter = accelerators->begin();
1817       iter != accelerators->end(); ++iter) {
1818    gtk_accel_group_connect(
1819        accel_group_,
1820        iter->second.GetGdkKeyCode(),
1821        static_cast<GdkModifierType>(iter->second.modifiers()),
1822        GtkAccelFlags(0),
1823        g_cclosure_new(G_CALLBACK(OnGtkAccelerator),
1824                       GINT_TO_POINTER(iter->first), NULL));
1825  }
1826}
1827
1828void BrowserWindowGtk::UpdateCustomFrame() {
1829  gtk_window_set_decorated(window_, !UseCustomFrame());
1830  titlebar_->UpdateCustomFrame(UseCustomFrame() && !IsFullscreen());
1831  UpdateWindowShape(bounds_.width(), bounds_.height());
1832}
1833
1834void BrowserWindowGtk::SaveWindowPosition() {
1835  // Browser::SaveWindowPlacement is used for session restore.
1836  if (browser_->ShouldSaveWindowPlacement())
1837    browser_->SaveWindowPlacement(restored_bounds_, IsMaximized());
1838
1839  // We also need to save the placement for startup.
1840  // This is a web of calls between views and delegates on Windows, but the
1841  // crux of the logic follows.  See also cocoa/browser_window_controller.mm.
1842  if (!browser_->profile()->GetPrefs())
1843    return;
1844
1845  std::string window_name = browser_->GetWindowPlacementKey();
1846  DictionaryPrefUpdate update(browser_->profile()->GetPrefs(),
1847                              window_name.c_str());
1848  DictionaryValue* window_preferences = update.Get();
1849  // Note that we store left/top for consistency with Windows, but that we
1850  // *don't* obey them; we only use them for computing width/height.  See
1851  // comments in SetGeometryHints().
1852  window_preferences->SetInteger("left", restored_bounds_.x());
1853  window_preferences->SetInteger("top", restored_bounds_.y());
1854  window_preferences->SetInteger("right", restored_bounds_.right());
1855  window_preferences->SetInteger("bottom", restored_bounds_.bottom());
1856  window_preferences->SetBoolean("maximized", IsMaximized());
1857
1858  scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_info_provider(
1859      WindowSizer::CreateDefaultMonitorInfoProvider());
1860  gfx::Rect work_area(
1861      monitor_info_provider->GetMonitorWorkAreaMatching(restored_bounds_));
1862  window_preferences->SetInteger("work_area_left", work_area.x());
1863  window_preferences->SetInteger("work_area_top", work_area.y());
1864  window_preferences->SetInteger("work_area_right", work_area.right());
1865  window_preferences->SetInteger("work_area_bottom", work_area.bottom());
1866}
1867
1868void BrowserWindowGtk::SetInfoBarShowing(InfoBar* bar, bool animate) {
1869  infobar_arrow_model_.ShowArrowFor(bar, animate);
1870}
1871
1872void BrowserWindowGtk::PaintStateChanged() {
1873  InvalidateInfoBarBits();
1874}
1875
1876void BrowserWindowGtk::InvalidateInfoBarBits() {
1877  gtk_widget_queue_draw(toolbar_border_);
1878  gtk_widget_queue_draw(toolbar_->widget());
1879  if (bookmark_bar_.get() && !bookmark_bar_is_floating_)
1880    gtk_widget_queue_draw(bookmark_bar_->widget());
1881}
1882
1883int BrowserWindowGtk::GetXPositionOfLocationIcon(GtkWidget* relative_to) {
1884  GtkWidget* location_icon = toolbar_->GetLocationBarView()->
1885      location_icon_widget();
1886  int x = 0;
1887  gtk_widget_translate_coordinates(
1888      location_icon, relative_to,
1889      (location_icon->allocation.width + 1) / 2,
1890      0, &x, NULL);
1891
1892  if (GTK_WIDGET_NO_WINDOW(relative_to))
1893    x += relative_to->allocation.x;
1894
1895  return x;
1896}
1897
1898void BrowserWindowGtk::OnLocationIconSizeAllocate(GtkWidget* sender,
1899                                                  GtkAllocation* allocation) {
1900  // The position of the arrow may have changed, so we'll have to redraw it.
1901  InvalidateInfoBarBits();
1902}
1903
1904gboolean BrowserWindowGtk::OnExposeDrawInfobarBits(GtkWidget* sender,
1905                                                   GdkEventExpose* expose) {
1906  if (!infobar_arrow_model_.NeedToDrawInfoBarArrow())
1907    return FALSE;
1908
1909  int x = GetXPositionOfLocationIcon(sender);
1910
1911  gfx::Rect toolbar_border(toolbar_border_->allocation);
1912  int y = 0;
1913  gtk_widget_translate_coordinates(toolbar_border_, sender,
1914                                   0, toolbar_border.bottom(),
1915                                   NULL, &y);
1916  if (GTK_WIDGET_NO_WINDOW(sender))
1917    y += sender->allocation.y;
1918
1919  // x, y is the bottom middle of the arrow. Now we need to create the bounding
1920  // rectangle.
1921  gfx::Size arrow_size = GetInfobarArrowSize();
1922  gfx::Rect bounds(
1923      gfx::Point(x - arrow_size.width() / 2.0, y - arrow_size.height()),
1924      arrow_size);
1925
1926  Profile* profile = browser()->profile();
1927  infobar_arrow_model_.Paint(
1928      sender, expose, bounds,
1929      GtkThemeService::GetFrom(profile)->GetBorderColor());
1930  return FALSE;
1931}
1932
1933gfx::Size BrowserWindowGtk::GetInfobarArrowSize() {
1934  static const size_t kDefaultWidth =
1935      2 * InfoBarArrowModel::kDefaultArrowSize;
1936  static const size_t kDefaultHeight = InfoBarArrowModel::kDefaultArrowSize;
1937  static const size_t kMaxWidth = 30;
1938  static const size_t kMaxHeight = 24;
1939
1940  double progress = bookmark_bar_.get() && !bookmark_bar_is_floating_ ?
1941      bookmark_bar_->animation()->GetCurrentValue() : 0.0;
1942  size_t width = kDefaultWidth + (kMaxWidth - kDefaultWidth) * progress;
1943  size_t height = kDefaultHeight + (kMaxHeight - kDefaultHeight) * progress;
1944
1945  return gfx::Size(width, height);
1946}
1947
1948gboolean BrowserWindowGtk::OnBookmarkBarExpose(GtkWidget* sender,
1949                                               GdkEventExpose* expose) {
1950  if (!infobar_arrow_model_.NeedToDrawInfoBarArrow())
1951    return FALSE;
1952
1953  if (bookmark_bar_is_floating_)
1954    return FALSE;
1955
1956  return OnExposeDrawInfobarBits(sender, expose);
1957}
1958
1959void BrowserWindowGtk::OnBookmarkBarSizeAllocate(GtkWidget* sender,
1960                                                 GtkAllocation* allocation) {
1961  // The size of the bookmark bar affects how the infobar arrow is drawn on
1962  // the toolbar.
1963  if (infobar_arrow_model_.NeedToDrawInfoBarArrow())
1964    InvalidateInfoBarBits();
1965}
1966
1967// static
1968gboolean BrowserWindowGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
1969                                            GObject* acceleratable,
1970                                            guint keyval,
1971                                            GdkModifierType modifier,
1972                                            void* user_data) {
1973  int command_id = GPOINTER_TO_INT(user_data);
1974  BrowserWindowGtk* browser_window =
1975      GetBrowserWindowForNativeWindow(GTK_WINDOW(acceleratable));
1976  DCHECK(browser_window != NULL);
1977  return browser_window->browser()->ExecuteCommandIfEnabled(command_id);
1978}
1979
1980// Let the focused widget have first crack at the key event so we don't
1981// override their accelerators.
1982gboolean BrowserWindowGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) {
1983  // If a widget besides the native view is focused, we have to try to handle
1984  // the custom accelerators before letting it handle them.
1985  TabContents* current_tab_contents =
1986      browser()->GetSelectedTabContents();
1987  // The current tab might not have a render view if it crashed.
1988  if (!current_tab_contents || !current_tab_contents->GetContentNativeView() ||
1989      !gtk_widget_is_focus(current_tab_contents->GetContentNativeView())) {
1990    int command_id = GetCustomCommandId(event);
1991    if (command_id == -1)
1992      command_id = GetPreHandleCommandId(event);
1993
1994    if (command_id != -1 && browser_->ExecuteCommandIfEnabled(command_id))
1995      return TRUE;
1996
1997    // Propagate the key event to child widget first, so we don't override their
1998    // accelerators.
1999    if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) {
2000      if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) {
2001        gtk_bindings_activate_event(GTK_OBJECT(widget), event);
2002      }
2003    }
2004  } else {
2005    bool rv = gtk_window_propagate_key_event(GTK_WINDOW(widget), event);
2006    DCHECK(rv);
2007  }
2008
2009  // Prevents the default handler from handling this event.
2010  return TRUE;
2011}
2012
2013gboolean BrowserWindowGtk::OnMouseMoveEvent(GtkWidget* widget,
2014                                            GdkEventMotion* event) {
2015  // This method is used to update the mouse cursor when over the edge of the
2016  // custom frame.  If the custom frame is off or we're over some other widget,
2017  // do nothing.
2018  if (!UseCustomFrame() || event->window != widget->window) {
2019    // Reset the cursor.
2020    if (frame_cursor_) {
2021      frame_cursor_ = NULL;
2022      gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL);
2023    }
2024    return FALSE;
2025  }
2026
2027  // Update the cursor if we're on the custom frame border.
2028  GdkWindowEdge edge;
2029  bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x),
2030                                    static_cast<int>(event->y), &edge);
2031  GdkCursorType new_cursor = GDK_LAST_CURSOR;
2032  if (has_hit_edge)
2033    new_cursor = GdkWindowEdgeToGdkCursorType(edge);
2034
2035  GdkCursorType last_cursor = GDK_LAST_CURSOR;
2036  if (frame_cursor_)
2037    last_cursor = frame_cursor_->type;
2038
2039  if (last_cursor != new_cursor) {
2040    if (has_hit_edge) {
2041      frame_cursor_ = gfx::GetCursor(new_cursor);
2042    } else {
2043      frame_cursor_ = NULL;
2044    }
2045    gdk_window_set_cursor(GTK_WIDGET(window_)->window, frame_cursor_);
2046  }
2047  return FALSE;
2048}
2049
2050gboolean BrowserWindowGtk::OnButtonPressEvent(GtkWidget* widget,
2051                                              GdkEventButton* event) {
2052  // Handle back/forward.
2053  if (event->type == GDK_BUTTON_PRESS) {
2054    if (event->button == 8) {
2055      browser_->GoBack(CURRENT_TAB);
2056      return TRUE;
2057    } else if (event->button == 9) {
2058      browser_->GoForward(CURRENT_TAB);
2059      return TRUE;
2060    }
2061  }
2062
2063  // Handle left, middle and right clicks.  In particular, we care about clicks
2064  // in the custom frame border and clicks in the titlebar.
2065
2066  // Make the button press coordinate relative to the browser window.
2067  int win_x, win_y;
2068  gdk_window_get_origin(GTK_WIDGET(window_)->window, &win_x, &win_y);
2069
2070  GdkWindowEdge edge;
2071  gfx::Point point(static_cast<int>(event->x_root - win_x),
2072                   static_cast<int>(event->y_root - win_y));
2073  bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge);
2074
2075  // Ignore clicks that are in/below the browser toolbar.
2076  GtkWidget* toolbar = toolbar_->widget();
2077  if (!GTK_WIDGET_VISIBLE(toolbar)) {
2078    // If the toolbar is not showing, use the location of web contents as the
2079    // boundary of where to ignore clicks.
2080    toolbar = render_area_vbox_;
2081  }
2082  gint toolbar_y;
2083  gtk_widget_get_pointer(toolbar, NULL, &toolbar_y);
2084  bool has_hit_titlebar = !IsFullscreen() && (toolbar_y < 0)
2085                          && !has_hit_edge;
2086  if (event->button == 1) {
2087    if (GDK_BUTTON_PRESS == event->type) {
2088      guint32 last_click_time = last_click_time_;
2089      gfx::Point last_click_position = last_click_position_;
2090      last_click_time_ = event->time;
2091      last_click_position_ = gfx::Point(static_cast<int>(event->x),
2092                                        static_cast<int>(event->y));
2093
2094      // Raise the window after a click on either the titlebar or the border to
2095      // match the behavior of most window managers, unless that behavior has
2096      // been suppressed.
2097      if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_)
2098        gdk_window_raise(GTK_WIDGET(window_)->window);
2099
2100      if (has_hit_titlebar) {
2101        // We want to start a move when the user single clicks, but not start a
2102        // move when the user double clicks.  However, a double click sends the
2103        // following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE,
2104        // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE.  If we
2105        // start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS,
2106        // the call to gtk_window_maximize fails.  To work around this, we
2107        // keep track of the last click and if it's going to be a double click,
2108        // we don't call gtk_window_begin_move_drag.
2109        static GtkSettings* settings = gtk_settings_get_default();
2110        gint double_click_time = 250;
2111        gint double_click_distance = 5;
2112        g_object_get(G_OBJECT(settings),
2113                     "gtk-double-click-time", &double_click_time,
2114                     "gtk-double-click-distance", &double_click_distance,
2115                     NULL);
2116
2117        guint32 click_time = event->time - last_click_time;
2118        int click_move_x = abs(event->x - last_click_position.x());
2119        int click_move_y = abs(event->y - last_click_position.y());
2120
2121        if (click_time > static_cast<guint32>(double_click_time) ||
2122            click_move_x > double_click_distance ||
2123            click_move_y > double_click_distance) {
2124          // Ignore drag requests if the window is the size of the screen.
2125          // We do this to avoid triggering fullscreen mode in metacity
2126          // (without the --no-force-fullscreen flag) and in compiz (with
2127          // Legacy Fullscreen Mode enabled).
2128          if (!BoundsMatchMonitorSize()) {
2129            gtk_window_begin_move_drag(window_, event->button,
2130                                       static_cast<gint>(event->x_root),
2131                                       static_cast<gint>(event->y_root),
2132                                       event->time);
2133          }
2134          return TRUE;
2135        }
2136      } else if (has_hit_edge) {
2137        gtk_window_begin_resize_drag(window_, edge, event->button,
2138                                     static_cast<gint>(event->x_root),
2139                                     static_cast<gint>(event->y_root),
2140                                     event->time);
2141        return TRUE;
2142      }
2143    } else if (GDK_2BUTTON_PRESS == event->type) {
2144      if (has_hit_titlebar) {
2145        // Maximize/restore on double click.
2146        if (IsMaximized()) {
2147          UnMaximize();
2148        } else {
2149          gtk_window_maximize(window_);
2150        }
2151        return TRUE;
2152      }
2153    }
2154  } else if (event->button == 2) {
2155    if (has_hit_titlebar || has_hit_edge) {
2156      gdk_window_lower(GTK_WIDGET(window_)->window);
2157    }
2158    return TRUE;
2159  } else if (event->button == 3) {
2160    if (has_hit_titlebar) {
2161      titlebar_->ShowContextMenu(event);
2162      return TRUE;
2163    }
2164  }
2165
2166  return FALSE;  // Continue to propagate the event.
2167}
2168
2169// static
2170void BrowserWindowGtk::MainWindowMapped(GtkWidget* widget) {
2171  // Map the X Window ID of the window to our window.
2172  XID xid = ui::GetX11WindowFromGtkWidget(widget);
2173  BrowserWindowGtk::xid_map_.insert(
2174      std::pair<XID, GtkWindow*>(xid, GTK_WINDOW(widget)));
2175}
2176
2177// static
2178void BrowserWindowGtk::MainWindowUnMapped(GtkWidget* widget) {
2179  // Unmap the X Window ID.
2180  XID xid = ui::GetX11WindowFromGtkWidget(widget);
2181  BrowserWindowGtk::xid_map_.erase(xid);
2182}
2183
2184gboolean BrowserWindowGtk::OnFocusIn(GtkWidget* widget,
2185                                     GdkEventFocus* event) {
2186  BrowserList::SetLastActive(browser_.get());
2187  return FALSE;
2188}
2189
2190gboolean BrowserWindowGtk::OnFocusOut(GtkWidget* widget,
2191                                      GdkEventFocus* event) {
2192  return FALSE;
2193}
2194
2195void BrowserWindowGtk::ShowSupportedWindowFeatures() {
2196  if (IsTabStripSupported())
2197    tabstrip_->Show();
2198
2199  if (IsToolbarSupported()) {
2200    toolbar_->Show();
2201    gtk_widget_show(toolbar_border_);
2202    gdk_window_lower(toolbar_border_->window);
2203  }
2204
2205  if (IsBookmarkBarSupported())
2206    MaybeShowBookmarkBar(false);
2207}
2208
2209void BrowserWindowGtk::HideUnsupportedWindowFeatures() {
2210  if (!IsTabStripSupported())
2211    tabstrip_->Hide();
2212
2213  if (!IsToolbarSupported())
2214    toolbar_->Hide();
2215
2216  // If the bookmark bar shelf is unsupported, then we never create it.
2217}
2218
2219bool BrowserWindowGtk::IsTabStripSupported() const {
2220  return browser_->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
2221}
2222
2223bool BrowserWindowGtk::IsToolbarSupported() const {
2224  return browser_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) ||
2225         browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
2226}
2227
2228bool BrowserWindowGtk::IsBookmarkBarSupported() const {
2229  return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR);
2230}
2231
2232bool BrowserWindowGtk::UsingCustomPopupFrame() const {
2233  GtkThemeService* theme_provider = GtkThemeService::GetFrom(
2234      browser()->profile());
2235  return !theme_provider->UseGtkTheme() &&
2236      browser()->type() & Browser::TYPE_POPUP;
2237}
2238
2239bool BrowserWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) {
2240  if (!UseCustomFrame())
2241    return false;
2242
2243  if (IsMaximized() || IsFullscreen())
2244    return false;
2245
2246  if (x < kFrameBorderThickness) {
2247    // Left edge.
2248    if (y < kResizeAreaCornerSize - kTopResizeAdjust) {
2249      *edge = GDK_WINDOW_EDGE_NORTH_WEST;
2250    } else if (y < bounds_.height() - kResizeAreaCornerSize) {
2251      *edge = GDK_WINDOW_EDGE_WEST;
2252    } else {
2253      *edge = GDK_WINDOW_EDGE_SOUTH_WEST;
2254    }
2255    return true;
2256  } else if (x < bounds_.width() - kFrameBorderThickness) {
2257    if (y < kFrameBorderThickness - kTopResizeAdjust) {
2258      // Top edge.
2259      if (x < kResizeAreaCornerSize) {
2260        *edge = GDK_WINDOW_EDGE_NORTH_WEST;
2261      } else if (x < bounds_.width() - kResizeAreaCornerSize) {
2262        *edge = GDK_WINDOW_EDGE_NORTH;
2263      } else {
2264        *edge = GDK_WINDOW_EDGE_NORTH_EAST;
2265      }
2266    } else if (y < bounds_.height() - kFrameBorderThickness) {
2267      // Ignore the middle content area.
2268      return false;
2269    } else {
2270      // Bottom edge.
2271      if (x < kResizeAreaCornerSize) {
2272        *edge = GDK_WINDOW_EDGE_SOUTH_WEST;
2273      } else if (x < bounds_.width() - kResizeAreaCornerSize) {
2274        *edge = GDK_WINDOW_EDGE_SOUTH;
2275      } else {
2276        *edge = GDK_WINDOW_EDGE_SOUTH_EAST;
2277      }
2278    }
2279    return true;
2280  } else {
2281    // Right edge.
2282    if (y < kResizeAreaCornerSize - kTopResizeAdjust) {
2283      *edge = GDK_WINDOW_EDGE_NORTH_EAST;
2284    } else if (y < bounds_.height() - kResizeAreaCornerSize) {
2285      *edge = GDK_WINDOW_EDGE_EAST;
2286    } else {
2287      *edge = GDK_WINDOW_EDGE_SOUTH_EAST;
2288    }
2289    return true;
2290  }
2291
2292  NOTREACHED();
2293  return false;
2294}
2295
2296bool BrowserWindowGtk::UseCustomFrame() {
2297  // We don't use the custom frame for app mode windows or app window popups.
2298  return use_custom_frame_pref_.GetValue() &&
2299      browser_->type() != Browser::TYPE_APP &&
2300      browser_->type() != Browser::TYPE_APP_POPUP;
2301}
2302
2303bool BrowserWindowGtk::BoundsMatchMonitorSize() {
2304  // A screen can be composed of multiple monitors.
2305  GdkScreen* screen = gtk_window_get_screen(window_);
2306  gint monitor_num = gdk_screen_get_monitor_at_window(screen,
2307      GTK_WIDGET(window_)->window);
2308
2309  GdkRectangle monitor_size;
2310  gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size);
2311  return bounds_.size() == gfx::Size(monitor_size.width, monitor_size.height);
2312}
2313
2314void BrowserWindowGtk::PlaceBookmarkBar(bool is_floating) {
2315  GtkWidget* parent = gtk_widget_get_parent(bookmark_bar_->widget());
2316  if (parent)
2317    gtk_container_remove(GTK_CONTAINER(parent), bookmark_bar_->widget());
2318
2319  if (!is_floating) {
2320    // Place the bookmark bar at the end of |window_vbox_|; this happens after
2321    // we have placed the render area at the end of |window_vbox_| so we will
2322    // be above the render area.
2323    gtk_box_pack_end(GTK_BOX(window_vbox_), bookmark_bar_->widget(),
2324                     FALSE, FALSE, 0);
2325  } else {
2326    // Place the bookmark bar at the end of the render area; this happens after
2327    // the tab contents container has been placed there so we will be
2328    // above the webpage (in terms of y).
2329    gtk_box_pack_end(GTK_BOX(render_area_vbox_), bookmark_bar_->widget(),
2330                     FALSE, FALSE, 0);
2331  }
2332}
2333
2334// static
2335bool BrowserWindowGtk::GetCustomFramePrefDefault() {
2336  std::string wm_name;
2337  if (!ui::GetWindowManagerName(&wm_name))
2338    return false;
2339
2340  // Ideally, we'd use the custom frame by default and just fall back on using
2341  // system decorations for the few (?) tiling window managers where the custom
2342  // frame doesn't make sense (e.g. awesome, ion3, ratpoison, xmonad, etc.) or
2343  // other WMs where it has issues (e.g. Fluxbox -- see issue 19130).  The EWMH
2344  // _NET_SUPPORTING_WM property makes it easy to look up a name for the current
2345  // WM, but at least some of the WMs in the latter group don't set it.
2346  // Instead, we default to using system decorations for all WMs and
2347  // special-case the ones where the custom frame should be used.  These names
2348  // are taken from the WMs' source code.
2349  return (wm_name == "Blackbox" ||
2350          wm_name == "compiz" ||
2351          wm_name == "e16" ||  // Enlightenment DR16
2352          wm_name == "Metacity" ||
2353          wm_name == "Mutter" ||
2354          wm_name == "Openbox" ||
2355          wm_name == "Xfwm4");
2356}
2357