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_bind.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 == NULL)
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 != NULL) {
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,const RootWindowConfig & config,const CefBrowserSettings & settings)110 void RootWindowGtk::Init(RootWindow::Delegate* delegate,
111 const 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::Bind(&RootWindowGtk::CreateRootWindow, this, settings,
129 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), NULL);
361 g_signal_connect(back_button_, "clicked",
362 G_CALLBACK(&RootWindowGtk::BackButtonClicked), this);
363 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), back_button_, -1 /* append */);
364
365 forward_button_ = gtk_tool_button_new(
366 gtk_image_new_from_icon_name("go-next", GTK_ICON_SIZE_MENU), NULL);
367 g_signal_connect(forward_button_, "clicked",
368 G_CALLBACK(&RootWindowGtk::ForwardButtonClicked), this);
369 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), forward_button_, -1 /* append */);
370
371 reload_button_ = gtk_tool_button_new(
372 gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_MENU), NULL);
373 g_signal_connect(reload_button_, "clicked",
374 G_CALLBACK(&RootWindowGtk::ReloadButtonClicked), this);
375 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), reload_button_, -1 /* append */);
376
377 stop_button_ = gtk_tool_button_new(
378 gtk_image_new_from_icon_name("process-stop", GTK_ICON_SIZE_MENU), NULL);
379 g_signal_connect(stop_button_, "clicked",
380 G_CALLBACK(&RootWindowGtk::StopButtonClicked), this);
381 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), stop_button_, -1 /* append */);
382
383 url_entry_ = gtk_entry_new();
384 g_signal_connect(url_entry_, "activate",
385 G_CALLBACK(&RootWindowGtk::URLEntryActivate), this);
386 g_signal_connect(url_entry_, "button-press-event",
387 G_CALLBACK(&RootWindowGtk::URLEntryButtonPress), this);
388
389 GtkToolItem* tool_item = gtk_tool_item_new();
390 gtk_container_add(GTK_CONTAINER(tool_item), url_entry_);
391 gtk_tool_item_set_expand(tool_item, TRUE);
392 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1); // append
393
394 gtk_grid_attach_next_to(GTK_GRID(grid), toolbar, menu_bar, GTK_POS_BOTTOM,
395 1, 1);
396 }
397
398 // Realize (show) the GTK widget. This must be done before the browser is
399 // created because the underlying X11 Window is required. |browser_bounds_|
400 // will be set at this point based on the GTK *SizeAllocated signal callbacks.
401 Show(ShowNormal);
402
403 // Most window managers ignore requests for initial window positions (instead
404 // using a user-defined placement algorithm) and honor requests after the
405 // window has already been shown.
406 gtk_window_move(GTK_WINDOW(window_), x, y);
407
408 // Windowed browsers are parented to the X11 Window underlying the GtkWindow*
409 // and must be sized manually. The OSR GTK widget, on the other hand, can be
410 // added to the grid container for automatic layout-based sizing.
411 GtkWidget* parent = with_osr_ ? grid : window_;
412
413 // Set the Display associated with the browser.
414 ::Display* xdisplay = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(window_));
415 CHECK(xdisplay);
416 if (with_osr_) {
417 static_cast<BrowserWindowOsrGtk*>(browser_window_.get())
418 ->set_xdisplay(xdisplay);
419 } else {
420 static_cast<BrowserWindowStdGtk*>(browser_window_.get())
421 ->set_xdisplay(xdisplay);
422 }
423
424 if (!is_popup_) {
425 // Create the browser window.
426 browser_window_->CreateBrowser(parent, browser_bounds_, settings, nullptr,
427 delegate_->GetRequestContext(this));
428 } else {
429 // With popups we already have a browser window. Parent the browser window
430 // to the root window and show it in the correct location.
431 browser_window_->ShowPopup(parent, browser_bounds_.x, browser_bounds_.y,
432 browser_bounds_.width, browser_bounds_.height);
433 }
434 }
435
OnBrowserCreated(CefRefPtr<CefBrowser> browser)436 void RootWindowGtk::OnBrowserCreated(CefRefPtr<CefBrowser> browser) {
437 REQUIRE_MAIN_THREAD();
438
439 // For popup browsers create the root window once the browser has been
440 // created.
441 if (is_popup_)
442 CreateRootWindow(CefBrowserSettings(), false);
443
444 delegate_->OnBrowserCreated(this, browser);
445 }
446
OnBrowserWindowClosing()447 void RootWindowGtk::OnBrowserWindowClosing() {
448 if (!CefCurrentlyOn(TID_UI)) {
449 CefPostTask(TID_UI,
450 base::Bind(&RootWindowGtk::OnBrowserWindowClosing, this));
451 return;
452 }
453
454 is_closing_ = true;
455 }
456
OnBrowserWindowDestroyed()457 void RootWindowGtk::OnBrowserWindowDestroyed() {
458 REQUIRE_MAIN_THREAD();
459
460 browser_window_.reset();
461
462 if (!window_destroyed_) {
463 // The browser was destroyed first. This could be due to the use of
464 // off-screen rendering or execution of JavaScript window.close().
465 // Close the RootWindow.
466 Close(true);
467 }
468
469 NotifyDestroyedIfDone(false, true);
470 }
471
OnSetAddress(const std::string & url)472 void RootWindowGtk::OnSetAddress(const std::string& url) {
473 REQUIRE_MAIN_THREAD();
474
475 if (url_entry_) {
476 ScopedGdkThreadsEnter scoped_gdk_threads;
477
478 std::string urlStr(url);
479 gtk_entry_set_text(GTK_ENTRY(url_entry_), urlStr.c_str());
480 }
481 }
482
OnSetTitle(const std::string & title)483 void RootWindowGtk::OnSetTitle(const std::string& title) {
484 REQUIRE_MAIN_THREAD();
485
486 if (window_) {
487 ScopedGdkThreadsEnter scoped_gdk_threads;
488
489 std::string titleStr(title);
490 gtk_window_set_title(GTK_WINDOW(window_), titleStr.c_str());
491 }
492 }
493
OnSetFullscreen(bool fullscreen)494 void RootWindowGtk::OnSetFullscreen(bool fullscreen) {
495 REQUIRE_MAIN_THREAD();
496
497 CefRefPtr<CefBrowser> browser = GetBrowser();
498 if (browser) {
499 scoped_ptr<window_test::WindowTestRunnerGtk> test_runner(
500 new window_test::WindowTestRunnerGtk());
501 if (fullscreen)
502 test_runner->Maximize(browser);
503 else
504 test_runner->Restore(browser);
505 }
506 }
507
OnAutoResize(const CefSize & new_size)508 void RootWindowGtk::OnAutoResize(const CefSize& new_size) {
509 REQUIRE_MAIN_THREAD();
510
511 if (!window_)
512 return;
513
514 ScopedGdkThreadsEnter scoped_gdk_threads;
515
516 GtkWindow* window = GTK_WINDOW(window_);
517 GdkWindow* gdk_window = gtk_widget_get_window(window_);
518
519 // Make sure the window isn't minimized or maximized.
520 if (IsWindowMaximized(window))
521 gtk_window_unmaximize(window);
522 else
523 gtk_window_present(window);
524
525 gdk_window_resize(gdk_window, new_size.width, new_size.height);
526 }
527
OnSetLoadingState(bool isLoading,bool canGoBack,bool canGoForward)528 void RootWindowGtk::OnSetLoadingState(bool isLoading,
529 bool canGoBack,
530 bool canGoForward) {
531 REQUIRE_MAIN_THREAD();
532
533 if (with_controls_) {
534 ScopedGdkThreadsEnter scoped_gdk_threads;
535
536 gtk_widget_set_sensitive(GTK_WIDGET(stop_button_), isLoading);
537 gtk_widget_set_sensitive(GTK_WIDGET(reload_button_), !isLoading);
538 gtk_widget_set_sensitive(GTK_WIDGET(back_button_), canGoBack);
539 gtk_widget_set_sensitive(GTK_WIDGET(forward_button_), canGoForward);
540 }
541 }
542
OnSetDraggableRegions(const std::vector<CefDraggableRegion> & regions)543 void RootWindowGtk::OnSetDraggableRegions(
544 const std::vector<CefDraggableRegion>& regions) {
545 REQUIRE_MAIN_THREAD();
546 // TODO(cef): Implement support for draggable regions on this platform.
547 }
548
NotifyMoveOrResizeStarted()549 void RootWindowGtk::NotifyMoveOrResizeStarted() {
550 if (!CURRENTLY_ON_MAIN_THREAD()) {
551 MAIN_POST_CLOSURE(
552 base::Bind(&RootWindowGtk::NotifyMoveOrResizeStarted, this));
553 return;
554 }
555
556 // Called when size, position or stack order changes.
557 CefRefPtr<CefBrowser> browser = GetBrowser();
558 if (browser.get()) {
559 // Notify the browser of move/resize events so that:
560 // - Popup windows are displayed in the correct location and dismissed
561 // when the window moves.
562 // - Drag&drop areas are updated accordingly.
563 browser->GetHost()->NotifyMoveOrResizeStarted();
564 }
565 }
566
NotifySetFocus()567 void RootWindowGtk::NotifySetFocus() {
568 if (!CURRENTLY_ON_MAIN_THREAD()) {
569 MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifySetFocus, this));
570 return;
571 }
572
573 if (!browser_window_.get())
574 return;
575
576 browser_window_->SetFocus(true);
577 delegate_->OnRootWindowActivated(this);
578 }
579
NotifyVisibilityChange(bool show)580 void RootWindowGtk::NotifyVisibilityChange(bool show) {
581 if (!CURRENTLY_ON_MAIN_THREAD()) {
582 MAIN_POST_CLOSURE(
583 base::Bind(&RootWindowGtk::NotifyVisibilityChange, this, show));
584 return;
585 }
586
587 if (!browser_window_.get())
588 return;
589
590 if (show)
591 browser_window_->Show();
592 else
593 browser_window_->Hide();
594 }
595
NotifyMenuBarHeight(int height)596 void RootWindowGtk::NotifyMenuBarHeight(int height) {
597 if (!CURRENTLY_ON_MAIN_THREAD()) {
598 MAIN_POST_CLOSURE(
599 base::Bind(&RootWindowGtk::NotifyMenuBarHeight, this, height));
600 return;
601 }
602
603 menubar_height_ = height;
604 }
605
NotifyContentBounds(int x,int y,int width,int height)606 void RootWindowGtk::NotifyContentBounds(int x, int y, int width, int height) {
607 if (!CURRENTLY_ON_MAIN_THREAD()) {
608 MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyContentBounds, this, x,
609 y, width, height));
610 return;
611 }
612
613 // Offset browser positioning by any controls that will appear in the client
614 // area.
615 const int ux_height = toolbar_height_ + menubar_height_;
616 const int browser_x = x;
617 const int browser_y = y + ux_height;
618 const int browser_width = width;
619 const int browser_height = height - ux_height;
620
621 // Size the browser window to match the GTK widget.
622 browser_bounds_ =
623 CefRect(browser_x, browser_y, browser_width, browser_height);
624 if (browser_window_.get()) {
625 browser_window_->SetBounds(browser_x, browser_y, browser_width,
626 browser_height);
627 }
628 }
629
NotifyLoadURL(const std::string & url)630 void RootWindowGtk::NotifyLoadURL(const std::string& url) {
631 if (!CURRENTLY_ON_MAIN_THREAD()) {
632 MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyLoadURL, this, url));
633 return;
634 }
635
636 CefRefPtr<CefBrowser> browser = GetBrowser();
637 if (browser.get()) {
638 browser->GetMainFrame()->LoadURL(url);
639 }
640 }
641
NotifyButtonClicked(int id)642 void RootWindowGtk::NotifyButtonClicked(int id) {
643 if (!CURRENTLY_ON_MAIN_THREAD()) {
644 MAIN_POST_CLOSURE(
645 base::Bind(&RootWindowGtk::NotifyButtonClicked, this, id));
646 return;
647 }
648
649 CefRefPtr<CefBrowser> browser = GetBrowser();
650 if (!browser.get())
651 return;
652
653 switch (id) {
654 case IDC_NAV_BACK:
655 browser->GoBack();
656 break;
657 case IDC_NAV_FORWARD:
658 browser->GoForward();
659 break;
660 case IDC_NAV_RELOAD:
661 browser->Reload();
662 break;
663 case IDC_NAV_STOP:
664 browser->StopLoad();
665 break;
666 default:
667 NOTREACHED() << "id=" << id;
668 }
669 }
670
NotifyMenuItem(int id)671 void RootWindowGtk::NotifyMenuItem(int id) {
672 if (!CURRENTLY_ON_MAIN_THREAD()) {
673 MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyMenuItem, this, id));
674 return;
675 }
676
677 // Run the test.
678 if (delegate_)
679 delegate_->OnTest(this, id);
680 }
681
NotifyForceClose()682 void RootWindowGtk::NotifyForceClose() {
683 if (!CefCurrentlyOn(TID_UI)) {
684 CefPostTask(TID_UI, base::Bind(&RootWindowGtk::NotifyForceClose, this));
685 return;
686 }
687
688 force_close_ = true;
689 }
690
NotifyCloseBrowser()691 void RootWindowGtk::NotifyCloseBrowser() {
692 if (!CURRENTLY_ON_MAIN_THREAD()) {
693 MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyCloseBrowser, this));
694 return;
695 }
696
697 CefRefPtr<CefBrowser> browser = GetBrowser();
698 if (browser) {
699 browser->GetHost()->CloseBrowser(false);
700 }
701 }
702
NotifyDestroyedIfDone(bool window_destroyed,bool browser_destroyed)703 void RootWindowGtk::NotifyDestroyedIfDone(bool window_destroyed,
704 bool browser_destroyed) {
705 // Each call will to this method will set only one state flag.
706 DCHECK_EQ(1, window_destroyed + browser_destroyed);
707
708 if (!CURRENTLY_ON_MAIN_THREAD()) {
709 MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyDestroyedIfDone, this,
710 window_destroyed, browser_destroyed));
711 return;
712 }
713
714 if (window_destroyed)
715 window_destroyed_ = true;
716 if (browser_destroyed)
717 browser_destroyed_ = true;
718
719 // Notify once both the window and the browser have been destroyed.
720 if (window_destroyed_ && browser_destroyed_)
721 delegate_->OnRootWindowDestroyed(this);
722 }
723
724 // static
WindowFocusIn(GtkWidget * widget,GdkEventFocus * event,RootWindowGtk * self)725 gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget,
726 GdkEventFocus* event,
727 RootWindowGtk* self) {
728 REQUIRE_MAIN_THREAD();
729
730 if (event->in) {
731 self->NotifySetFocus();
732
733 // Return true for a windowed browser so that focus is not passed to GTK.
734 return self->with_osr_ ? FALSE : TRUE;
735 }
736
737 return FALSE;
738 }
739
740 // static
WindowState(GtkWidget * widget,GdkEventWindowState * event,RootWindowGtk * self)741 gboolean RootWindowGtk::WindowState(GtkWidget* widget,
742 GdkEventWindowState* event,
743 RootWindowGtk* self) {
744 REQUIRE_MAIN_THREAD();
745
746 // Called when the root window is iconified or restored. Hide the browser
747 // window when the root window is iconified to reduce resource usage.
748 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
749 self->NotifyVisibilityChange(
750 !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED));
751 }
752
753 return TRUE;
754 }
755
756 // static
WindowConfigure(GtkWindow * window,GdkEvent * event,RootWindowGtk * self)757 gboolean RootWindowGtk::WindowConfigure(GtkWindow* window,
758 GdkEvent* event,
759 RootWindowGtk* self) {
760 REQUIRE_MAIN_THREAD();
761 self->NotifyMoveOrResizeStarted();
762 return FALSE; // Don't stop this message.
763 }
764
765 // static
WindowDestroy(GtkWidget * widget,RootWindowGtk * self)766 void RootWindowGtk::WindowDestroy(GtkWidget* widget, RootWindowGtk* self) {
767 // May be called on the main thread or the UI thread.
768 self->NotifyDestroyedIfDone(true, false);
769 }
770
771 // static
WindowDelete(GtkWidget * widget,GdkEvent * event,RootWindowGtk * self)772 gboolean RootWindowGtk::WindowDelete(GtkWidget* widget,
773 GdkEvent* event,
774 RootWindowGtk* self) {
775 REQUIRE_MAIN_THREAD();
776
777 // Called to query whether the root window should be closed.
778 if (self->force_close_)
779 return FALSE; // Allow the close.
780
781 if (!self->is_closing_) {
782 // Notify the browser window that we would like to close it. This
783 // will result in a call to ClientHandler::DoClose() if the
784 // JavaScript 'onbeforeunload' event handler allows it.
785 self->NotifyCloseBrowser();
786
787 // Cancel the close.
788 return TRUE;
789 }
790
791 // Allow the close.
792 return FALSE;
793 }
794
795 // static
GridSizeAllocated(GtkWidget * widget,GtkAllocation * allocation,RootWindowGtk * self)796 void RootWindowGtk::GridSizeAllocated(GtkWidget* widget,
797 GtkAllocation* allocation,
798 RootWindowGtk* self) {
799 // May be called on the main thread and the UI thread.
800 self->NotifyContentBounds(allocation->x, allocation->y, allocation->width,
801 allocation->height);
802 }
803
804 // static
MenubarSizeAllocated(GtkWidget * widget,GtkAllocation * allocation,RootWindowGtk * self)805 void RootWindowGtk::MenubarSizeAllocated(GtkWidget* widget,
806 GtkAllocation* allocation,
807 RootWindowGtk* self) {
808 // May be called on the main thread and the UI thread.
809 self->NotifyMenuBarHeight(allocation->height);
810 }
811
812 // static
MenuItemActivated(GtkWidget * widget,RootWindowGtk * self)813 gboolean RootWindowGtk::MenuItemActivated(GtkWidget* widget,
814 RootWindowGtk* self) {
815 REQUIRE_MAIN_THREAD();
816
817 // Retrieve the menu ID set in AddMenuEntry.
818 int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), kMenuIdKey));
819 self->NotifyMenuItem(id);
820
821 return FALSE; // Don't stop this message.
822 }
823
824 // static
ToolbarSizeAllocated(GtkWidget * widget,GtkAllocation * allocation,RootWindowGtk * self)825 void RootWindowGtk::ToolbarSizeAllocated(GtkWidget* widget,
826 GtkAllocation* allocation,
827 RootWindowGtk* self) {
828 self->toolbar_height_ = allocation->height;
829 }
830
831 // static
BackButtonClicked(GtkButton * button,RootWindowGtk * self)832 void RootWindowGtk::BackButtonClicked(GtkButton* button, RootWindowGtk* self) {
833 REQUIRE_MAIN_THREAD();
834 self->NotifyButtonClicked(IDC_NAV_BACK);
835 }
836
837 // static
ForwardButtonClicked(GtkButton * button,RootWindowGtk * self)838 void RootWindowGtk::ForwardButtonClicked(GtkButton* button,
839 RootWindowGtk* self) {
840 REQUIRE_MAIN_THREAD();
841 self->NotifyButtonClicked(IDC_NAV_FORWARD);
842 }
843
844 // static
StopButtonClicked(GtkButton * button,RootWindowGtk * self)845 void RootWindowGtk::StopButtonClicked(GtkButton* button, RootWindowGtk* self) {
846 REQUIRE_MAIN_THREAD();
847 self->NotifyButtonClicked(IDC_NAV_STOP);
848 }
849
850 // static
ReloadButtonClicked(GtkButton * button,RootWindowGtk * self)851 void RootWindowGtk::ReloadButtonClicked(GtkButton* button,
852 RootWindowGtk* self) {
853 REQUIRE_MAIN_THREAD();
854 self->NotifyButtonClicked(IDC_NAV_RELOAD);
855 }
856
857 // static
URLEntryActivate(GtkEntry * entry,RootWindowGtk * self)858 void RootWindowGtk::URLEntryActivate(GtkEntry* entry, RootWindowGtk* self) {
859 REQUIRE_MAIN_THREAD();
860 const gchar* url = gtk_entry_get_text(entry);
861 self->NotifyLoadURL(std::string(url));
862 }
863
864 // static
URLEntryButtonPress(GtkWidget * widget,GdkEventButton * event,RootWindowGtk * self)865 gboolean RootWindowGtk::URLEntryButtonPress(GtkWidget* widget,
866 GdkEventButton* event,
867 RootWindowGtk* self) {
868 REQUIRE_MAIN_THREAD();
869
870 // Give focus to the GTK window. This is a work-around for bad focus-related
871 // interaction between the root window managed by GTK and the browser managed
872 // by X11.
873 GtkWidget* window = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
874 GdkWindow* gdk_window = gtk_widget_get_window(window);
875 ::Display* xdisplay = GDK_WINDOW_XDISPLAY(gdk_window);
876 ::Window xwindow = GDK_WINDOW_XID(gdk_window);
877
878 // Retrieve the atoms required by the below XSendEvent call.
879 const char* kAtoms[] = {"WM_PROTOCOLS", "WM_TAKE_FOCUS"};
880 Atom atoms[2];
881 int result =
882 XInternAtoms(xdisplay, const_cast<char**>(kAtoms), 2, false, atoms);
883 if (!result)
884 NOTREACHED();
885
886 XEvent e;
887 e.type = ClientMessage;
888 e.xany.display = xdisplay;
889 e.xany.window = xwindow;
890 e.xclient.format = 32;
891 e.xclient.message_type = atoms[0];
892 e.xclient.data.l[0] = atoms[1];
893 e.xclient.data.l[1] = CurrentTime;
894 e.xclient.data.l[2] = 0;
895 e.xclient.data.l[3] = 0;
896 e.xclient.data.l[4] = 0;
897
898 XSendEvent(xdisplay, xwindow, false, 0, &e);
899
900 return FALSE;
901 }
902
CreateMenuBar()903 GtkWidget* RootWindowGtk::CreateMenuBar() {
904 GtkWidget* menu_bar = gtk_menu_bar_new();
905
906 // Create the test menu.
907 GtkWidget* test_menu = CreateMenu(menu_bar, "Tests");
908 AddMenuEntry(test_menu, "Get Source", ID_TESTS_GETSOURCE);
909 AddMenuEntry(test_menu, "Get Text", ID_TESTS_GETTEXT);
910 AddMenuEntry(test_menu, "New Window", ID_TESTS_WINDOW_NEW);
911 AddMenuEntry(test_menu, "Popup Window", ID_TESTS_WINDOW_POPUP);
912 AddMenuEntry(test_menu, "Request", ID_TESTS_REQUEST);
913 AddMenuEntry(test_menu, "Plugin Info", ID_TESTS_PLUGIN_INFO);
914 AddMenuEntry(test_menu, "Zoom In", ID_TESTS_ZOOM_IN);
915 AddMenuEntry(test_menu, "Zoom Out", ID_TESTS_ZOOM_OUT);
916 AddMenuEntry(test_menu, "Zoom Reset", ID_TESTS_ZOOM_RESET);
917 if (with_osr_) {
918 AddMenuEntry(test_menu, "Set FPS", ID_TESTS_OSR_FPS);
919 AddMenuEntry(test_menu, "Set Scale Factor", ID_TESTS_OSR_DSF);
920 }
921 AddMenuEntry(test_menu, "Begin Tracing", ID_TESTS_TRACING_BEGIN);
922 AddMenuEntry(test_menu, "End Tracing", ID_TESTS_TRACING_END);
923 AddMenuEntry(test_menu, "Print", ID_TESTS_PRINT);
924 AddMenuEntry(test_menu, "Print to PDF", ID_TESTS_PRINT_TO_PDF);
925 AddMenuEntry(test_menu, "Mute Audio", ID_TESTS_MUTE_AUDIO);
926 AddMenuEntry(test_menu, "Unmute Audio", ID_TESTS_UNMUTE_AUDIO);
927 AddMenuEntry(test_menu, "Other Tests", ID_TESTS_OTHER_TESTS);
928
929 return menu_bar;
930 }
931
CreateMenu(GtkWidget * menu_bar,const char * text)932 GtkWidget* RootWindowGtk::CreateMenu(GtkWidget* menu_bar, const char* text) {
933 GtkWidget* menu_widget = gtk_menu_new();
934 GtkWidget* menu_header = gtk_menu_item_new_with_label(text);
935 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_header), menu_widget);
936 gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_header);
937 return menu_widget;
938 }
939
AddMenuEntry(GtkWidget * menu_widget,const char * text,int id)940 GtkWidget* RootWindowGtk::AddMenuEntry(GtkWidget* menu_widget,
941 const char* text,
942 int id) {
943 GtkWidget* entry = gtk_menu_item_new_with_label(text);
944 g_signal_connect(entry, "activate",
945 G_CALLBACK(&RootWindowGtk::MenuItemActivated), this);
946
947 // Set the menu ID that will be retrieved in MenuItemActivated.
948 g_object_set_data(G_OBJECT(entry), kMenuIdKey, GINT_TO_POINTER(id));
949
950 gtk_menu_shell_append(GTK_MENU_SHELL(menu_widget), entry);
951 return entry;
952 }
953
954 } // namespace client
955