• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4 
5 #include "tests/cefclient/browser/root_window_gtk.h"
6 
7 #include <gdk/gdk.h>
8 #include <gdk/gdkx.h>
9 
10 #include <X11/Xlib.h>
11 #undef Success     // Definition conflicts with cef_message_router.h
12 #undef RootWindow  // Definition conflicts with root_window.h
13 
14 #include "include/base/cef_callback.h"
15 #include "include/cef_app.h"
16 #include "tests/cefclient/browser/browser_window_osr_gtk.h"
17 #include "tests/cefclient/browser/browser_window_std_gtk.h"
18 #include "tests/cefclient/browser/main_context.h"
19 #include "tests/cefclient/browser/resource.h"
20 #include "tests/cefclient/browser/temp_window.h"
21 #include "tests/cefclient/browser/util_gtk.h"
22 #include "tests/cefclient/browser/window_test_runner_gtk.h"
23 #include "tests/shared/browser/main_message_loop.h"
24 #include "tests/shared/common/client_switches.h"
25 
26 namespace client {
27 
28 namespace {
29 
30 const char kMenuIdKey[] = "menu_id";
31 
UseDefaultX11VisualForGtk(GtkWidget * widget)32 void UseDefaultX11VisualForGtk(GtkWidget* widget) {
33 #if GTK_CHECK_VERSION(3, 15, 1)
34   // GTK+ > 3.15.1 uses an X11 visual optimized for GTK+'s OpenGL stuff
35   // since revid dae447728d: https://github.com/GNOME/gtk/commit/dae447728d
36   // However, it breaks CEF: https://github.com/cztomczak/cefcapi/issues/9
37   // Let's use the default X11 visual instead of the GTK's blessed one.
38   // Copied from: https://github.com/cztomczak/cefcapi.
39   GdkScreen* screen = gdk_screen_get_default();
40   GList* visuals = gdk_screen_list_visuals(screen);
41 
42   GdkX11Screen* x11_screen = GDK_X11_SCREEN(screen);
43   if (x11_screen == nullptr)
44     return;
45 
46   Visual* default_xvisual = DefaultVisual(GDK_SCREEN_XDISPLAY(x11_screen),
47                                           GDK_SCREEN_XNUMBER(x11_screen));
48   GList* cursor = visuals;
49   while (cursor != nullptr) {
50     GdkVisual* visual = GDK_X11_VISUAL(cursor->data);
51     if (default_xvisual->visualid ==
52         gdk_x11_visual_get_xvisual(visual)->visualid) {
53       gtk_widget_set_visual(widget, visual);
54       break;
55     }
56     cursor = cursor->next;
57   }
58   g_list_free(visuals);
59 #endif
60 }
61 
IsWindowMaximized(GtkWindow * window)62 bool IsWindowMaximized(GtkWindow* window) {
63   GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
64   gint state = gdk_window_get_state(gdk_window);
65   return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false;
66 }
67 
MinimizeWindow(GtkWindow * window)68 void MinimizeWindow(GtkWindow* window) {
69   // Unmaximize the window before minimizing so restore behaves correctly.
70   if (IsWindowMaximized(window))
71     gtk_window_unmaximize(window);
72 
73   gtk_window_iconify(window);
74 }
75 
MaximizeWindow(GtkWindow * window)76 void MaximizeWindow(GtkWindow* window) {
77   gtk_window_maximize(window);
78 }
79 
80 }  // namespace
81 
RootWindowGtk()82 RootWindowGtk::RootWindowGtk()
83     : with_controls_(false),
84       always_on_top_(false),
85       with_osr_(false),
86       with_extension_(false),
87       is_popup_(false),
88       initialized_(false),
89       window_(nullptr),
90       back_button_(nullptr),
91       forward_button_(nullptr),
92       reload_button_(nullptr),
93       stop_button_(nullptr),
94       url_entry_(nullptr),
95       toolbar_height_(0),
96       menubar_height_(0),
97       window_destroyed_(false),
98       browser_destroyed_(false),
99       force_close_(false),
100       is_closing_(false) {}
101 
~RootWindowGtk()102 RootWindowGtk::~RootWindowGtk() {
103   REQUIRE_MAIN_THREAD();
104 
105   // The window and browser should already have been destroyed.
106   DCHECK(window_destroyed_);
107   DCHECK(browser_destroyed_);
108 }
109 
Init(RootWindow::Delegate * delegate,std::unique_ptr<RootWindowConfig> config,const CefBrowserSettings & settings)110 void RootWindowGtk::Init(RootWindow::Delegate* delegate,
111                          std::unique_ptr<RootWindowConfig> config,
112                          const CefBrowserSettings& settings) {
113   DCHECK(delegate);
114   DCHECK(!initialized_);
115 
116   delegate_ = delegate;
117   with_controls_ = config->with_controls;
118   always_on_top_ = config->always_on_top;
119   with_osr_ = config->with_osr;
120   with_extension_ = config->with_extension;
121   start_rect_ = config->bounds;
122 
123   CreateBrowserWindow(config->url);
124 
125   initialized_ = true;
126 
127   // Always post asynchronously to avoid reentrancy of the GDK lock.
128   MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::CreateRootWindow, this,
129                                    settings, config->initially_hidden));
130 }
131 
InitAsPopup(RootWindow::Delegate * delegate,bool with_controls,bool with_osr,const CefPopupFeatures & popupFeatures,CefWindowInfo & windowInfo,CefRefPtr<CefClient> & client,CefBrowserSettings & settings)132 void RootWindowGtk::InitAsPopup(RootWindow::Delegate* delegate,
133                                 bool with_controls,
134                                 bool with_osr,
135                                 const CefPopupFeatures& popupFeatures,
136                                 CefWindowInfo& windowInfo,
137                                 CefRefPtr<CefClient>& client,
138                                 CefBrowserSettings& settings) {
139   DCHECK(delegate);
140   DCHECK(!initialized_);
141 
142   delegate_ = delegate;
143   with_controls_ = with_controls;
144   with_osr_ = with_osr;
145   is_popup_ = true;
146 
147   if (popupFeatures.xSet)
148     start_rect_.x = popupFeatures.x;
149   if (popupFeatures.ySet)
150     start_rect_.y = popupFeatures.y;
151   if (popupFeatures.widthSet)
152     start_rect_.width = popupFeatures.width;
153   if (popupFeatures.heightSet)
154     start_rect_.height = popupFeatures.height;
155 
156   CreateBrowserWindow(std::string());
157 
158   initialized_ = true;
159 
160   // The new popup is initially parented to a temporary window. The native root
161   // window will be created after the browser is created and the popup window
162   // will be re-parented to it at that time.
163   browser_window_->GetPopupConfig(TempWindow::GetWindowHandle(), windowInfo,
164                                   client, settings);
165 }
166 
Show(ShowMode mode)167 void RootWindowGtk::Show(ShowMode mode) {
168   REQUIRE_MAIN_THREAD();
169 
170   if (!window_)
171     return;
172 
173   ScopedGdkThreadsEnter scoped_gdk_threads;
174 
175   // Show the GTK window.
176   UseDefaultX11VisualForGtk(GTK_WIDGET(window_));
177   gtk_widget_show_all(window_);
178 
179   if (mode == ShowMinimized)
180     MinimizeWindow(GTK_WINDOW(window_));
181   else if (mode == ShowMaximized)
182     MaximizeWindow(GTK_WINDOW(window_));
183 
184   // Flush the display to make sure the underlying X11 window gets created
185   // immediately.
186   GdkWindow* gdk_window = gtk_widget_get_window(window_);
187   GdkDisplay* display = gdk_window_get_display(gdk_window);
188   gdk_display_flush(display);
189 }
190 
Hide()191 void RootWindowGtk::Hide() {
192   REQUIRE_MAIN_THREAD();
193 
194   ScopedGdkThreadsEnter scoped_gdk_threads;
195 
196   if (window_)
197     gtk_widget_hide(window_);
198 }
199 
SetBounds(int x,int y,size_t width,size_t height)200 void RootWindowGtk::SetBounds(int x, int y, size_t width, size_t height) {
201   REQUIRE_MAIN_THREAD();
202 
203   if (!window_)
204     return;
205 
206   ScopedGdkThreadsEnter scoped_gdk_threads;
207 
208   GtkWindow* window = GTK_WINDOW(window_);
209   GdkWindow* gdk_window = gtk_widget_get_window(window_);
210 
211   // Make sure the window isn't minimized or maximized.
212   if (IsWindowMaximized(window))
213     gtk_window_unmaximize(window);
214   else
215     gtk_window_present(window);
216 
217   gdk_window_move_resize(gdk_window, x, y, width, height);
218 }
219 
Close(bool force)220 void RootWindowGtk::Close(bool force) {
221   REQUIRE_MAIN_THREAD();
222 
223   if (window_) {
224     ScopedGdkThreadsEnter scoped_gdk_threads;
225 
226     if (force) {
227       NotifyForceClose();
228     }
229     gtk_widget_destroy(window_);
230   }
231 }
232 
SetDeviceScaleFactor(float device_scale_factor)233 void RootWindowGtk::SetDeviceScaleFactor(float device_scale_factor) {
234   REQUIRE_MAIN_THREAD();
235 
236   if (browser_window_ && with_osr_)
237     browser_window_->SetDeviceScaleFactor(device_scale_factor);
238 }
239 
GetDeviceScaleFactor() const240 float RootWindowGtk::GetDeviceScaleFactor() const {
241   REQUIRE_MAIN_THREAD();
242 
243   if (browser_window_ && with_osr_)
244     return browser_window_->GetDeviceScaleFactor();
245 
246   NOTREACHED();
247   return 0.0f;
248 }
249 
GetBrowser() const250 CefRefPtr<CefBrowser> RootWindowGtk::GetBrowser() const {
251   REQUIRE_MAIN_THREAD();
252 
253   if (browser_window_)
254     return browser_window_->GetBrowser();
255   return nullptr;
256 }
257 
GetWindowHandle() const258 ClientWindowHandle RootWindowGtk::GetWindowHandle() const {
259   REQUIRE_MAIN_THREAD();
260   return window_;
261 }
262 
WithWindowlessRendering() const263 bool RootWindowGtk::WithWindowlessRendering() const {
264   REQUIRE_MAIN_THREAD();
265   return with_osr_;
266 }
267 
WithExtension() const268 bool RootWindowGtk::WithExtension() const {
269   REQUIRE_MAIN_THREAD();
270   return with_extension_;
271 }
272 
CreateBrowserWindow(const std::string & startup_url)273 void RootWindowGtk::CreateBrowserWindow(const std::string& startup_url) {
274   if (with_osr_) {
275     OsrRendererSettings settings = {};
276     MainContext::Get()->PopulateOsrSettings(&settings);
277     browser_window_.reset(new BrowserWindowOsrGtk(this, startup_url, settings));
278   } else {
279     browser_window_.reset(new BrowserWindowStdGtk(this, startup_url));
280   }
281 }
282 
CreateRootWindow(const CefBrowserSettings & settings,bool initially_hidden)283 void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings,
284                                      bool initially_hidden) {
285   REQUIRE_MAIN_THREAD();
286   DCHECK(!window_);
287 
288   // TODO(port): If no x,y position is specified the window will always appear
289   // in the upper-left corner. Maybe there's a better default place to put it?
290   int x = start_rect_.x;
291   int y = start_rect_.y;
292   int width, height;
293   if (start_rect_.IsEmpty()) {
294     // TODO(port): Also, maybe there's a better way to choose the default size.
295     width = 800;
296     height = 600;
297   } else {
298     width = start_rect_.width;
299     height = start_rect_.height;
300   }
301 
302   ScopedGdkThreadsEnter scoped_gdk_threads;
303 
304   window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
305   CHECK(window_);
306 
307   if (always_on_top_) {
308     gtk_window_set_keep_above(GTK_WINDOW(window_), TRUE);
309   }
310 
311   gtk_window_set_default_size(GTK_WINDOW(window_), width, height);
312   g_signal_connect(G_OBJECT(window_), "focus-in-event",
313                    G_CALLBACK(&RootWindowGtk::WindowFocusIn), this);
314   g_signal_connect(G_OBJECT(window_), "window-state-event",
315                    G_CALLBACK(&RootWindowGtk::WindowState), this);
316   g_signal_connect(G_OBJECT(window_), "configure-event",
317                    G_CALLBACK(&RootWindowGtk::WindowConfigure), this);
318   g_signal_connect(G_OBJECT(window_), "destroy",
319                    G_CALLBACK(&RootWindowGtk::WindowDestroy), this);
320   g_signal_connect(G_OBJECT(window_), "delete_event",
321                    G_CALLBACK(&RootWindowGtk::WindowDelete), this);
322 
323   const cef_color_t background_color = MainContext::Get()->GetBackgroundColor();
324   GdkRGBA rgba = {0};
325   rgba.red = CefColorGetR(background_color) * 65535 / 255;
326   rgba.green = CefColorGetG(background_color) * 65535 / 255;
327   rgba.blue = CefColorGetB(background_color) * 65535 / 255;
328   rgba.alpha = 1;
329 
330   gchar* css = g_strdup_printf("#* { background-color: %s; }",
331                                gdk_rgba_to_string(&rgba));
332   GtkCssProvider* provider = gtk_css_provider_new();
333   gtk_css_provider_load_from_data(provider, css, -1, nullptr);
334   g_free(css);
335   gtk_style_context_add_provider(gtk_widget_get_style_context(window_),
336                                  GTK_STYLE_PROVIDER(provider),
337                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
338   g_object_unref(provider);
339 
340   GtkWidget* grid = gtk_grid_new();
341   gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
342   g_signal_connect(grid, "size-allocate",
343                    G_CALLBACK(&RootWindowGtk::GridSizeAllocated), this);
344   gtk_container_add(GTK_CONTAINER(window_), grid);
345 
346   if (with_controls_) {
347     GtkWidget* menu_bar = CreateMenuBar();
348     g_signal_connect(menu_bar, "size-allocate",
349                      G_CALLBACK(&RootWindowGtk::MenubarSizeAllocated), this);
350 
351     gtk_grid_attach(GTK_GRID(grid), menu_bar, 0, 0, 1, 1);
352 
353     GtkWidget* toolbar = gtk_toolbar_new();
354     // Turn off the labels on the toolbar buttons.
355     gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
356     g_signal_connect(toolbar, "size-allocate",
357                      G_CALLBACK(&RootWindowGtk::ToolbarSizeAllocated), this);
358 
359     back_button_ = gtk_tool_button_new(
360         gtk_image_new_from_icon_name("go-previous", GTK_ICON_SIZE_MENU),
361         nullptr);
362     g_signal_connect(back_button_, "clicked",
363                      G_CALLBACK(&RootWindowGtk::BackButtonClicked), this);
364     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), back_button_, -1 /* append */);
365 
366     forward_button_ = gtk_tool_button_new(
367         gtk_image_new_from_icon_name("go-next", GTK_ICON_SIZE_MENU), nullptr);
368     g_signal_connect(forward_button_, "clicked",
369                      G_CALLBACK(&RootWindowGtk::ForwardButtonClicked), this);
370     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), forward_button_, -1 /* append */);
371 
372     reload_button_ = gtk_tool_button_new(
373         gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_MENU),
374         nullptr);
375     g_signal_connect(reload_button_, "clicked",
376                      G_CALLBACK(&RootWindowGtk::ReloadButtonClicked), this);
377     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), reload_button_, -1 /* append */);
378 
379     stop_button_ = gtk_tool_button_new(
380         gtk_image_new_from_icon_name("process-stop", GTK_ICON_SIZE_MENU),
381         nullptr);
382     g_signal_connect(stop_button_, "clicked",
383                      G_CALLBACK(&RootWindowGtk::StopButtonClicked), this);
384     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), stop_button_, -1 /* append */);
385 
386     url_entry_ = gtk_entry_new();
387     g_signal_connect(url_entry_, "activate",
388                      G_CALLBACK(&RootWindowGtk::URLEntryActivate), this);
389     g_signal_connect(url_entry_, "button-press-event",
390                      G_CALLBACK(&RootWindowGtk::URLEntryButtonPress), this);
391 
392     GtkToolItem* tool_item = gtk_tool_item_new();
393     gtk_container_add(GTK_CONTAINER(tool_item), url_entry_);
394     gtk_tool_item_set_expand(tool_item, TRUE);
395     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1);  // append
396 
397     gtk_grid_attach_next_to(GTK_GRID(grid), toolbar, menu_bar, GTK_POS_BOTTOM,
398                             1, 1);
399   }
400 
401   // Realize (show) the GTK widget. This must be done before the browser is
402   // created because the underlying X11 Window is required. |browser_bounds_|
403   // will be set at this point based on the GTK *SizeAllocated signal callbacks.
404   Show(ShowNormal);
405 
406   // Most window managers ignore requests for initial window positions (instead
407   // using a user-defined placement algorithm) and honor requests after the
408   // window has already been shown.
409   gtk_window_move(GTK_WINDOW(window_), x, y);
410 
411   // Windowed browsers are parented to the X11 Window underlying the GtkWindow*
412   // and must be sized manually. The OSR GTK widget, on the other hand, can be
413   // added to the grid container for automatic layout-based sizing.
414   GtkWidget* parent = with_osr_ ? grid : window_;
415 
416   // Set the Display associated with the browser.
417   ::Display* xdisplay = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(window_));
418   CHECK(xdisplay);
419   if (with_osr_) {
420     static_cast<BrowserWindowOsrGtk*>(browser_window_.get())
421         ->set_xdisplay(xdisplay);
422   } else {
423     static_cast<BrowserWindowStdGtk*>(browser_window_.get())
424         ->set_xdisplay(xdisplay);
425   }
426 
427   if (!is_popup_) {
428     // Create the browser window.
429     browser_window_->CreateBrowser(parent, browser_bounds_, settings, nullptr,
430                                    delegate_->GetRequestContext(this));
431   } else {
432     // With popups we already have a browser window. Parent the browser window
433     // to the root window and show it in the correct location.
434     browser_window_->ShowPopup(parent, browser_bounds_.x, browser_bounds_.y,
435                                browser_bounds_.width, browser_bounds_.height);
436   }
437 }
438 
OnBrowserCreated(CefRefPtr<CefBrowser> browser)439 void RootWindowGtk::OnBrowserCreated(CefRefPtr<CefBrowser> browser) {
440   REQUIRE_MAIN_THREAD();
441 
442   // For popup browsers create the root window once the browser has been
443   // created.
444   if (is_popup_)
445     CreateRootWindow(CefBrowserSettings(), false);
446 
447   delegate_->OnBrowserCreated(this, browser);
448 }
449 
OnBrowserWindowClosing()450 void RootWindowGtk::OnBrowserWindowClosing() {
451   if (!CefCurrentlyOn(TID_UI)) {
452     CefPostTask(TID_UI,
453                 base::BindOnce(&RootWindowGtk::OnBrowserWindowClosing, this));
454     return;
455   }
456 
457   is_closing_ = true;
458 }
459 
OnBrowserWindowDestroyed()460 void RootWindowGtk::OnBrowserWindowDestroyed() {
461   REQUIRE_MAIN_THREAD();
462 
463   browser_window_.reset();
464 
465   if (!window_destroyed_) {
466     // The browser was destroyed first. This could be due to the use of
467     // off-screen rendering or execution of JavaScript window.close().
468     // Close the RootWindow.
469     Close(true);
470   }
471 
472   NotifyDestroyedIfDone(false, true);
473 }
474 
OnSetAddress(const std::string & url)475 void RootWindowGtk::OnSetAddress(const std::string& url) {
476   REQUIRE_MAIN_THREAD();
477 
478   if (url_entry_) {
479     ScopedGdkThreadsEnter scoped_gdk_threads;
480 
481     std::string urlStr(url);
482     gtk_entry_set_text(GTK_ENTRY(url_entry_), urlStr.c_str());
483   }
484 }
485 
OnSetTitle(const std::string & title)486 void RootWindowGtk::OnSetTitle(const std::string& title) {
487   REQUIRE_MAIN_THREAD();
488 
489   if (window_) {
490     ScopedGdkThreadsEnter scoped_gdk_threads;
491 
492     std::string titleStr(title);
493     gtk_window_set_title(GTK_WINDOW(window_), titleStr.c_str());
494   }
495 }
496 
OnSetFullscreen(bool fullscreen)497 void RootWindowGtk::OnSetFullscreen(bool fullscreen) {
498   REQUIRE_MAIN_THREAD();
499 
500   CefRefPtr<CefBrowser> browser = GetBrowser();
501   if (browser) {
502     std::unique_ptr<window_test::WindowTestRunnerGtk> test_runner(
503         new window_test::WindowTestRunnerGtk());
504     if (fullscreen)
505       test_runner->Maximize(browser);
506     else
507       test_runner->Restore(browser);
508   }
509 }
510 
OnAutoResize(const CefSize & new_size)511 void RootWindowGtk::OnAutoResize(const CefSize& new_size) {
512   REQUIRE_MAIN_THREAD();
513 
514   if (!window_)
515     return;
516 
517   ScopedGdkThreadsEnter scoped_gdk_threads;
518 
519   GtkWindow* window = GTK_WINDOW(window_);
520   GdkWindow* gdk_window = gtk_widget_get_window(window_);
521 
522   // Make sure the window isn't minimized or maximized.
523   if (IsWindowMaximized(window))
524     gtk_window_unmaximize(window);
525   else
526     gtk_window_present(window);
527 
528   gdk_window_resize(gdk_window, new_size.width, new_size.height);
529 }
530 
OnSetLoadingState(bool isLoading,bool canGoBack,bool canGoForward)531 void RootWindowGtk::OnSetLoadingState(bool isLoading,
532                                       bool canGoBack,
533                                       bool canGoForward) {
534   REQUIRE_MAIN_THREAD();
535 
536   if (with_controls_) {
537     ScopedGdkThreadsEnter scoped_gdk_threads;
538 
539     gtk_widget_set_sensitive(GTK_WIDGET(stop_button_), isLoading);
540     gtk_widget_set_sensitive(GTK_WIDGET(reload_button_), !isLoading);
541     gtk_widget_set_sensitive(GTK_WIDGET(back_button_), canGoBack);
542     gtk_widget_set_sensitive(GTK_WIDGET(forward_button_), canGoForward);
543   }
544 }
545 
OnSetDraggableRegions(const std::vector<CefDraggableRegion> & regions)546 void RootWindowGtk::OnSetDraggableRegions(
547     const std::vector<CefDraggableRegion>& regions) {
548   REQUIRE_MAIN_THREAD();
549   // TODO(cef): Implement support for draggable regions on this platform.
550 }
551 
NotifyMoveOrResizeStarted()552 void RootWindowGtk::NotifyMoveOrResizeStarted() {
553   if (!CURRENTLY_ON_MAIN_THREAD()) {
554     MAIN_POST_CLOSURE(
555         base::BindOnce(&RootWindowGtk::NotifyMoveOrResizeStarted, this));
556     return;
557   }
558 
559   // Called when size, position or stack order changes.
560   CefRefPtr<CefBrowser> browser = GetBrowser();
561   if (browser.get()) {
562     // Notify the browser of move/resize events so that:
563     // - Popup windows are displayed in the correct location and dismissed
564     //   when the window moves.
565     // - Drag&drop areas are updated accordingly.
566     browser->GetHost()->NotifyMoveOrResizeStarted();
567   }
568 }
569 
NotifySetFocus()570 void RootWindowGtk::NotifySetFocus() {
571   if (!CURRENTLY_ON_MAIN_THREAD()) {
572     MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifySetFocus, this));
573     return;
574   }
575 
576   if (!browser_window_.get())
577     return;
578 
579   browser_window_->SetFocus(true);
580   delegate_->OnRootWindowActivated(this);
581 }
582 
NotifyVisibilityChange(bool show)583 void RootWindowGtk::NotifyVisibilityChange(bool show) {
584   if (!CURRENTLY_ON_MAIN_THREAD()) {
585     MAIN_POST_CLOSURE(
586         base::BindOnce(&RootWindowGtk::NotifyVisibilityChange, this, show));
587     return;
588   }
589 
590   if (!browser_window_.get())
591     return;
592 
593   if (show)
594     browser_window_->Show();
595   else
596     browser_window_->Hide();
597 }
598 
NotifyMenuBarHeight(int height)599 void RootWindowGtk::NotifyMenuBarHeight(int height) {
600   if (!CURRENTLY_ON_MAIN_THREAD()) {
601     MAIN_POST_CLOSURE(
602         base::BindOnce(&RootWindowGtk::NotifyMenuBarHeight, this, height));
603     return;
604   }
605 
606   menubar_height_ = height;
607 }
608 
NotifyContentBounds(int x,int y,int width,int height)609 void RootWindowGtk::NotifyContentBounds(int x, int y, int width, int height) {
610   if (!CURRENTLY_ON_MAIN_THREAD()) {
611     MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyContentBounds, this,
612                                      x, y, width, height));
613     return;
614   }
615 
616   // Offset browser positioning by any controls that will appear in the client
617   // area.
618   const int ux_height = toolbar_height_ + menubar_height_;
619   const int browser_x = x;
620   const int browser_y = y + ux_height;
621   const int browser_width = width;
622   const int browser_height = height - ux_height;
623 
624   // Size the browser window to match the GTK widget.
625   browser_bounds_ =
626       CefRect(browser_x, browser_y, browser_width, browser_height);
627   if (browser_window_.get()) {
628     browser_window_->SetBounds(browser_x, browser_y, browser_width,
629                                browser_height);
630   }
631 }
632 
NotifyLoadURL(const std::string & url)633 void RootWindowGtk::NotifyLoadURL(const std::string& url) {
634   if (!CURRENTLY_ON_MAIN_THREAD()) {
635     MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyLoadURL, this, url));
636     return;
637   }
638 
639   CefRefPtr<CefBrowser> browser = GetBrowser();
640   if (browser.get()) {
641     browser->GetMainFrame()->LoadURL(url);
642   }
643 }
644 
NotifyButtonClicked(int id)645 void RootWindowGtk::NotifyButtonClicked(int id) {
646   if (!CURRENTLY_ON_MAIN_THREAD()) {
647     MAIN_POST_CLOSURE(
648         base::BindOnce(&RootWindowGtk::NotifyButtonClicked, this, id));
649     return;
650   }
651 
652   CefRefPtr<CefBrowser> browser = GetBrowser();
653   if (!browser.get())
654     return;
655 
656   switch (id) {
657     case IDC_NAV_BACK:
658       browser->GoBack();
659       break;
660     case IDC_NAV_FORWARD:
661       browser->GoForward();
662       break;
663     case IDC_NAV_RELOAD:
664       browser->Reload();
665       break;
666     case IDC_NAV_STOP:
667       browser->StopLoad();
668       break;
669     default:
670       NOTREACHED() << "id=" << id;
671   }
672 }
673 
NotifyMenuItem(int id)674 void RootWindowGtk::NotifyMenuItem(int id) {
675   if (!CURRENTLY_ON_MAIN_THREAD()) {
676     MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyMenuItem, this, id));
677     return;
678   }
679 
680   // Run the test.
681   if (delegate_)
682     delegate_->OnTest(this, id);
683 }
684 
NotifyForceClose()685 void RootWindowGtk::NotifyForceClose() {
686   if (!CefCurrentlyOn(TID_UI)) {
687     CefPostTask(TID_UI, base::BindOnce(&RootWindowGtk::NotifyForceClose, this));
688     return;
689   }
690 
691   force_close_ = true;
692 }
693 
NotifyCloseBrowser()694 void RootWindowGtk::NotifyCloseBrowser() {
695   if (!CURRENTLY_ON_MAIN_THREAD()) {
696     MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyCloseBrowser, this));
697     return;
698   }
699 
700   CefRefPtr<CefBrowser> browser = GetBrowser();
701   if (browser) {
702     browser->GetHost()->CloseBrowser(false);
703   }
704 }
705 
NotifyDestroyedIfDone(bool window_destroyed,bool browser_destroyed)706 void RootWindowGtk::NotifyDestroyedIfDone(bool window_destroyed,
707                                           bool browser_destroyed) {
708   // Each call will to this method will set only one state flag.
709   DCHECK_EQ(1, window_destroyed + browser_destroyed);
710 
711   if (!CURRENTLY_ON_MAIN_THREAD()) {
712     MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyDestroyedIfDone,
713                                      this, window_destroyed,
714                                      browser_destroyed));
715     return;
716   }
717 
718   if (window_destroyed)
719     window_destroyed_ = true;
720   if (browser_destroyed)
721     browser_destroyed_ = true;
722 
723   // Notify once both the window and the browser have been destroyed.
724   if (window_destroyed_ && browser_destroyed_)
725     delegate_->OnRootWindowDestroyed(this);
726 }
727 
728 // static
WindowFocusIn(GtkWidget * widget,GdkEventFocus * event,RootWindowGtk * self)729 gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget,
730                                       GdkEventFocus* event,
731                                       RootWindowGtk* self) {
732   REQUIRE_MAIN_THREAD();
733 
734   if (event->in) {
735     self->NotifySetFocus();
736 
737     // Return true for a windowed browser so that focus is not passed to GTK.
738     return self->with_osr_ ? FALSE : TRUE;
739   }
740 
741   return FALSE;
742 }
743 
744 // static
WindowState(GtkWidget * widget,GdkEventWindowState * event,RootWindowGtk * self)745 gboolean RootWindowGtk::WindowState(GtkWidget* widget,
746                                     GdkEventWindowState* event,
747                                     RootWindowGtk* self) {
748   REQUIRE_MAIN_THREAD();
749 
750   // Called when the root window is iconified or restored. Hide the browser
751   // window when the root window is iconified to reduce resource usage.
752   if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
753     self->NotifyVisibilityChange(
754         !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED));
755   }
756 
757   return TRUE;
758 }
759 
760 // static
WindowConfigure(GtkWindow * window,GdkEvent * event,RootWindowGtk * self)761 gboolean RootWindowGtk::WindowConfigure(GtkWindow* window,
762                                         GdkEvent* event,
763                                         RootWindowGtk* self) {
764   REQUIRE_MAIN_THREAD();
765   self->NotifyMoveOrResizeStarted();
766   return FALSE;  // Don't stop this message.
767 }
768 
769 // static
WindowDestroy(GtkWidget * widget,RootWindowGtk * self)770 void RootWindowGtk::WindowDestroy(GtkWidget* widget, RootWindowGtk* self) {
771   // May be called on the main thread or the UI thread.
772   self->NotifyDestroyedIfDone(true, false);
773 }
774 
775 // static
WindowDelete(GtkWidget * widget,GdkEvent * event,RootWindowGtk * self)776 gboolean RootWindowGtk::WindowDelete(GtkWidget* widget,
777                                      GdkEvent* event,
778                                      RootWindowGtk* self) {
779   REQUIRE_MAIN_THREAD();
780 
781   // Called to query whether the root window should be closed.
782   if (self->force_close_)
783     return FALSE;  // Allow the close.
784 
785   if (!self->is_closing_) {
786     // Notify the browser window that we would like to close it. This
787     // will result in a call to ClientHandler::DoClose() if the
788     // JavaScript 'onbeforeunload' event handler allows it.
789     self->NotifyCloseBrowser();
790 
791     // Cancel the close.
792     return TRUE;
793   }
794 
795   // Allow the close.
796   return FALSE;
797 }
798 
799 // static
GridSizeAllocated(GtkWidget * widget,GtkAllocation * allocation,RootWindowGtk * self)800 void RootWindowGtk::GridSizeAllocated(GtkWidget* widget,
801                                       GtkAllocation* allocation,
802                                       RootWindowGtk* self) {
803   // May be called on the main thread and the UI thread.
804   self->NotifyContentBounds(allocation->x, allocation->y, allocation->width,
805                             allocation->height);
806 }
807 
808 // static
MenubarSizeAllocated(GtkWidget * widget,GtkAllocation * allocation,RootWindowGtk * self)809 void RootWindowGtk::MenubarSizeAllocated(GtkWidget* widget,
810                                          GtkAllocation* allocation,
811                                          RootWindowGtk* self) {
812   // May be called on the main thread and the UI thread.
813   self->NotifyMenuBarHeight(allocation->height);
814 }
815 
816 // static
MenuItemActivated(GtkWidget * widget,RootWindowGtk * self)817 gboolean RootWindowGtk::MenuItemActivated(GtkWidget* widget,
818                                           RootWindowGtk* self) {
819   REQUIRE_MAIN_THREAD();
820 
821   // Retrieve the menu ID set in AddMenuEntry.
822   int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), kMenuIdKey));
823   self->NotifyMenuItem(id);
824 
825   return FALSE;  // Don't stop this message.
826 }
827 
828 // static
ToolbarSizeAllocated(GtkWidget * widget,GtkAllocation * allocation,RootWindowGtk * self)829 void RootWindowGtk::ToolbarSizeAllocated(GtkWidget* widget,
830                                          GtkAllocation* allocation,
831                                          RootWindowGtk* self) {
832   self->toolbar_height_ = allocation->height;
833 }
834 
835 // static
BackButtonClicked(GtkButton * button,RootWindowGtk * self)836 void RootWindowGtk::BackButtonClicked(GtkButton* button, RootWindowGtk* self) {
837   REQUIRE_MAIN_THREAD();
838   self->NotifyButtonClicked(IDC_NAV_BACK);
839 }
840 
841 // static
ForwardButtonClicked(GtkButton * button,RootWindowGtk * self)842 void RootWindowGtk::ForwardButtonClicked(GtkButton* button,
843                                          RootWindowGtk* self) {
844   REQUIRE_MAIN_THREAD();
845   self->NotifyButtonClicked(IDC_NAV_FORWARD);
846 }
847 
848 // static
StopButtonClicked(GtkButton * button,RootWindowGtk * self)849 void RootWindowGtk::StopButtonClicked(GtkButton* button, RootWindowGtk* self) {
850   REQUIRE_MAIN_THREAD();
851   self->NotifyButtonClicked(IDC_NAV_STOP);
852 }
853 
854 // static
ReloadButtonClicked(GtkButton * button,RootWindowGtk * self)855 void RootWindowGtk::ReloadButtonClicked(GtkButton* button,
856                                         RootWindowGtk* self) {
857   REQUIRE_MAIN_THREAD();
858   self->NotifyButtonClicked(IDC_NAV_RELOAD);
859 }
860 
861 // static
URLEntryActivate(GtkEntry * entry,RootWindowGtk * self)862 void RootWindowGtk::URLEntryActivate(GtkEntry* entry, RootWindowGtk* self) {
863   REQUIRE_MAIN_THREAD();
864   const gchar* url = gtk_entry_get_text(entry);
865   self->NotifyLoadURL(std::string(url));
866 }
867 
868 // static
URLEntryButtonPress(GtkWidget * widget,GdkEventButton * event,RootWindowGtk * self)869 gboolean RootWindowGtk::URLEntryButtonPress(GtkWidget* widget,
870                                             GdkEventButton* event,
871                                             RootWindowGtk* self) {
872   REQUIRE_MAIN_THREAD();
873 
874   // Give focus to the GTK window. This is a work-around for bad focus-related
875   // interaction between the root window managed by GTK and the browser managed
876   // by X11.
877   GtkWidget* window = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
878   GdkWindow* gdk_window = gtk_widget_get_window(window);
879   ::Display* xdisplay = GDK_WINDOW_XDISPLAY(gdk_window);
880   ::Window xwindow = GDK_WINDOW_XID(gdk_window);
881 
882   // Retrieve the atoms required by the below XSendEvent call.
883   const char* kAtoms[] = {"WM_PROTOCOLS", "WM_TAKE_FOCUS"};
884   Atom atoms[2];
885   int result =
886       XInternAtoms(xdisplay, const_cast<char**>(kAtoms), 2, false, atoms);
887   if (!result)
888     NOTREACHED();
889 
890   XEvent e;
891   e.type = ClientMessage;
892   e.xany.display = xdisplay;
893   e.xany.window = xwindow;
894   e.xclient.format = 32;
895   e.xclient.message_type = atoms[0];
896   e.xclient.data.l[0] = atoms[1];
897   e.xclient.data.l[1] = CurrentTime;
898   e.xclient.data.l[2] = 0;
899   e.xclient.data.l[3] = 0;
900   e.xclient.data.l[4] = 0;
901 
902   XSendEvent(xdisplay, xwindow, false, 0, &e);
903 
904   return FALSE;
905 }
906 
CreateMenuBar()907 GtkWidget* RootWindowGtk::CreateMenuBar() {
908   GtkWidget* menu_bar = gtk_menu_bar_new();
909 
910   // Create the test menu.
911   GtkWidget* test_menu = CreateMenu(menu_bar, "Tests");
912   AddMenuEntry(test_menu, "Get Source", ID_TESTS_GETSOURCE);
913   AddMenuEntry(test_menu, "Get Text", ID_TESTS_GETTEXT);
914   AddMenuEntry(test_menu, "New Window", ID_TESTS_WINDOW_NEW);
915   AddMenuEntry(test_menu, "Popup Window", ID_TESTS_WINDOW_POPUP);
916   AddMenuEntry(test_menu, "Request", ID_TESTS_REQUEST);
917   AddMenuEntry(test_menu, "Plugin Info", ID_TESTS_PLUGIN_INFO);
918   AddMenuEntry(test_menu, "Zoom In", ID_TESTS_ZOOM_IN);
919   AddMenuEntry(test_menu, "Zoom Out", ID_TESTS_ZOOM_OUT);
920   AddMenuEntry(test_menu, "Zoom Reset", ID_TESTS_ZOOM_RESET);
921   if (with_osr_) {
922     AddMenuEntry(test_menu, "Set FPS", ID_TESTS_OSR_FPS);
923     AddMenuEntry(test_menu, "Set Scale Factor", ID_TESTS_OSR_DSF);
924   }
925   AddMenuEntry(test_menu, "Begin Tracing", ID_TESTS_TRACING_BEGIN);
926   AddMenuEntry(test_menu, "End Tracing", ID_TESTS_TRACING_END);
927   AddMenuEntry(test_menu, "Print", ID_TESTS_PRINT);
928   AddMenuEntry(test_menu, "Print to PDF", ID_TESTS_PRINT_TO_PDF);
929   AddMenuEntry(test_menu, "Mute Audio", ID_TESTS_MUTE_AUDIO);
930   AddMenuEntry(test_menu, "Unmute Audio", ID_TESTS_UNMUTE_AUDIO);
931   AddMenuEntry(test_menu, "Other Tests", ID_TESTS_OTHER_TESTS);
932 
933   return menu_bar;
934 }
935 
CreateMenu(GtkWidget * menu_bar,const char * text)936 GtkWidget* RootWindowGtk::CreateMenu(GtkWidget* menu_bar, const char* text) {
937   GtkWidget* menu_widget = gtk_menu_new();
938   GtkWidget* menu_header = gtk_menu_item_new_with_label(text);
939   gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_header), menu_widget);
940   gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_header);
941   return menu_widget;
942 }
943 
AddMenuEntry(GtkWidget * menu_widget,const char * text,int id)944 GtkWidget* RootWindowGtk::AddMenuEntry(GtkWidget* menu_widget,
945                                        const char* text,
946                                        int id) {
947   GtkWidget* entry = gtk_menu_item_new_with_label(text);
948   g_signal_connect(entry, "activate",
949                    G_CALLBACK(&RootWindowGtk::MenuItemActivated), this);
950 
951   // Set the menu ID that will be retrieved in MenuItemActivated.
952   g_object_set_data(G_OBJECT(entry), kMenuIdKey, GINT_TO_POINTER(id));
953 
954   gtk_menu_shell_append(GTK_MENU_SHELL(menu_widget), entry);
955   return entry;
956 }
957 
958 }  // namespace client
959