1 // Copyright (c) 2016 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_views.h"
6
7 #include <memory>
8
9 #include "include/base/cef_build.h"
10 #include "include/base/cef_callback.h"
11 #include "include/base/cef_cxx17_backports.h"
12 #include "include/cef_app.h"
13 #include "include/wrapper/cef_helpers.h"
14 #include "tests/cefclient/browser/client_handler_std.h"
15
16 namespace client {
17
18 namespace {
19
20 // Images that are loaded early and cached.
21 static const char* kDefaultImageCache[] = {"menu_icon", "window_icon"};
22
23 } // namespace
24
25 RootWindowViews::RootWindowViews() = default;
26
~RootWindowViews()27 RootWindowViews::~RootWindowViews() {
28 REQUIRE_MAIN_THREAD();
29 }
30
Init(RootWindow::Delegate * delegate,std::unique_ptr<RootWindowConfig> config,const CefBrowserSettings & settings)31 void RootWindowViews::Init(RootWindow::Delegate* delegate,
32 std::unique_ptr<RootWindowConfig> config,
33 const CefBrowserSettings& settings) {
34 DCHECK(delegate);
35 DCHECK(!config->with_osr); // Windowless rendering is not supported.
36 DCHECK(!initialized_);
37
38 delegate_ = delegate;
39 config_ = std::move(config);
40
41 if (config_->initially_hidden && !config_->source_bounds.IsEmpty()) {
42 // The window will be sized and positioned in OnAutoResize().
43 initial_bounds_ = config_->source_bounds;
44 position_on_resize_ = true;
45 } else {
46 initial_bounds_ = config_->bounds;
47 }
48
49 CreateClientHandler(config_->url);
50 initialized_ = true;
51
52 // Continue initialization on the UI thread.
53 InitOnUIThread(settings, config_->url, delegate_->GetRequestContext(this));
54 }
55
InitAsPopup(RootWindow::Delegate * delegate,bool with_controls,bool with_osr,const CefPopupFeatures & popupFeatures,CefWindowInfo & windowInfo,CefRefPtr<CefClient> & client,CefBrowserSettings & settings)56 void RootWindowViews::InitAsPopup(RootWindow::Delegate* delegate,
57 bool with_controls,
58 bool with_osr,
59 const CefPopupFeatures& popupFeatures,
60 CefWindowInfo& windowInfo,
61 CefRefPtr<CefClient>& client,
62 CefBrowserSettings& settings) {
63 CEF_REQUIRE_UI_THREAD();
64
65 DCHECK(delegate);
66 DCHECK(!with_osr); // Windowless rendering is not supported.
67 DCHECK(!initialized_);
68
69 delegate_ = delegate;
70 config_ = std::make_unique<RootWindowConfig>();
71 config_->with_controls = with_controls;
72 is_popup_ = true;
73
74 if (popupFeatures.xSet)
75 initial_bounds_.x = popupFeatures.x;
76 if (popupFeatures.ySet)
77 initial_bounds_.y = popupFeatures.y;
78 if (popupFeatures.widthSet)
79 initial_bounds_.width = popupFeatures.width;
80 if (popupFeatures.heightSet)
81 initial_bounds_.height = popupFeatures.height;
82
83 CreateClientHandler(std::string());
84 initialized_ = true;
85
86 // The Window will be created in ViewsWindow::OnPopupBrowserViewCreated().
87 client = client_handler_;
88 }
89
Show(ShowMode mode)90 void RootWindowViews::Show(ShowMode mode) {
91 if (!CefCurrentlyOn(TID_UI)) {
92 // Execute this method on the UI thread.
93 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::Show, this, mode));
94 return;
95 }
96
97 if (!window_)
98 return;
99
100 window_->Show();
101
102 switch (mode) {
103 case ShowMinimized:
104 window_->Minimize();
105 break;
106 case ShowMaximized:
107 window_->Maximize();
108 break;
109 default:
110 break;
111 }
112 }
113
Hide()114 void RootWindowViews::Hide() {
115 if (!CefCurrentlyOn(TID_UI)) {
116 // Execute this method on the UI thread.
117 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::Hide, this));
118 return;
119 }
120
121 if (window_)
122 window_->Hide();
123 }
124
SetBounds(int x,int y,size_t width,size_t height)125 void RootWindowViews::SetBounds(int x, int y, size_t width, size_t height) {
126 if (!CefCurrentlyOn(TID_UI)) {
127 // Execute this method on the UI thread.
128 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::SetBounds, this, x, y,
129 width, height));
130 return;
131 }
132
133 if (window_) {
134 window_->SetBounds(
135 CefRect(x, y, static_cast<int>(width), static_cast<int>(height)));
136 }
137 }
138
Close(bool force)139 void RootWindowViews::Close(bool force) {
140 if (!CefCurrentlyOn(TID_UI)) {
141 // Execute this method on the UI thread.
142 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::Close, this, force));
143 return;
144 }
145
146 if (window_)
147 window_->Close(force);
148 }
149
SetDeviceScaleFactor(float device_scale_factor)150 void RootWindowViews::SetDeviceScaleFactor(float device_scale_factor) {
151 REQUIRE_MAIN_THREAD();
152 // Windowless rendering is not supported.
153 NOTREACHED();
154 }
155
GetDeviceScaleFactor() const156 float RootWindowViews::GetDeviceScaleFactor() const {
157 REQUIRE_MAIN_THREAD();
158 // Windowless rendering is not supported.
159 NOTREACHED();
160 return 0.0;
161 }
162
GetBrowser() const163 CefRefPtr<CefBrowser> RootWindowViews::GetBrowser() const {
164 REQUIRE_MAIN_THREAD();
165 return browser_;
166 }
167
GetWindowHandle() const168 ClientWindowHandle RootWindowViews::GetWindowHandle() const {
169 REQUIRE_MAIN_THREAD();
170 #if defined(OS_LINUX)
171 // ClientWindowHandle is a GtkWidget* on Linux and we don't have one of those.
172 return nullptr;
173 #else
174 if (browser_)
175 return browser_->GetHost()->GetWindowHandle();
176 return kNullWindowHandle;
177 #endif
178 }
179
WithExtension() const180 bool RootWindowViews::WithExtension() const {
181 DCHECK(initialized_);
182 return config_->with_extension;
183 }
184
WithControls()185 bool RootWindowViews::WithControls() {
186 DCHECK(initialized_);
187 return config_->with_controls;
188 }
189
WithExtension()190 bool RootWindowViews::WithExtension() {
191 DCHECK(initialized_);
192 return config_->with_extension;
193 }
194
OnExtensionsChanged(const ExtensionSet & extensions)195 void RootWindowViews::OnExtensionsChanged(const ExtensionSet& extensions) {
196 if (!CefCurrentlyOn(TID_UI)) {
197 // Execute this method on the UI thread.
198 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::OnExtensionsChanged,
199 this, extensions));
200 return;
201 }
202
203 if (window_) {
204 window_->OnExtensionsChanged(extensions);
205 } else {
206 // Window may not exist yet for popups.
207 pending_extensions_ = extensions;
208 }
209 }
210
InitiallyHidden()211 bool RootWindowViews::InitiallyHidden() {
212 CEF_REQUIRE_UI_THREAD();
213 return config_->initially_hidden;
214 }
215
GetParentWindow()216 CefRefPtr<CefWindow> RootWindowViews::GetParentWindow() {
217 CEF_REQUIRE_UI_THREAD();
218 return config_->parent_window;
219 }
220
GetWindowBounds()221 CefRect RootWindowViews::GetWindowBounds() {
222 CEF_REQUIRE_UI_THREAD();
223 return initial_bounds_;
224 }
225
GetImageCache()226 scoped_refptr<ImageCache> RootWindowViews::GetImageCache() {
227 CEF_REQUIRE_UI_THREAD();
228 return image_cache_;
229 }
230
OnViewsWindowCreated(CefRefPtr<ViewsWindow> window)231 void RootWindowViews::OnViewsWindowCreated(CefRefPtr<ViewsWindow> window) {
232 CEF_REQUIRE_UI_THREAD();
233 DCHECK(!window_);
234 window_ = window;
235 window_->SetAlwaysOnTop(config_->always_on_top);
236
237 if (!pending_extensions_.empty()) {
238 window_->OnExtensionsChanged(pending_extensions_);
239 pending_extensions_.clear();
240 }
241 }
242
OnViewsWindowDestroyed(CefRefPtr<ViewsWindow> window)243 void RootWindowViews::OnViewsWindowDestroyed(CefRefPtr<ViewsWindow> window) {
244 CEF_REQUIRE_UI_THREAD();
245 window_ = nullptr;
246
247 // Continue on the main thread.
248 MAIN_POST_CLOSURE(
249 base::BindOnce(&RootWindowViews::NotifyViewsWindowDestroyed, this));
250 }
251
OnViewsWindowActivated(CefRefPtr<ViewsWindow> window)252 void RootWindowViews::OnViewsWindowActivated(CefRefPtr<ViewsWindow> window) {
253 CEF_REQUIRE_UI_THREAD();
254
255 // Continue on the main thread.
256 MAIN_POST_CLOSURE(
257 base::BindOnce(&RootWindowViews::NotifyViewsWindowActivated, this));
258 }
259
GetDelegateForPopup(CefRefPtr<CefClient> client)260 ViewsWindow::Delegate* RootWindowViews::GetDelegateForPopup(
261 CefRefPtr<CefClient> client) {
262 CEF_REQUIRE_UI_THREAD();
263 // |handler| was created in RootWindowViews::InitAsPopup().
264 ClientHandlerStd* handler = static_cast<ClientHandlerStd*>(client.get());
265 RootWindowViews* root_window =
266 static_cast<RootWindowViews*>(handler->delegate());
267
268 // Transfer some state to the child RootWindowViews.
269 root_window->image_cache_ = image_cache_;
270
271 return root_window;
272 }
273
CreateExtensionWindow(CefRefPtr<CefExtension> extension,const CefRect & source_bounds,CefRefPtr<CefWindow> parent_window,base::OnceClosure close_callback)274 void RootWindowViews::CreateExtensionWindow(CefRefPtr<CefExtension> extension,
275 const CefRect& source_bounds,
276 CefRefPtr<CefWindow> parent_window,
277 base::OnceClosure close_callback) {
278 if (!CURRENTLY_ON_MAIN_THREAD()) {
279 // Execute this method on the main thread.
280 MAIN_POST_CLOSURE(base::BindOnce(&RootWindowViews::CreateExtensionWindow,
281 this, extension, source_bounds,
282 parent_window, std::move(close_callback)));
283 return;
284 }
285
286 delegate_->CreateExtensionWindow(extension, source_bounds, parent_window,
287 std::move(close_callback), false);
288 }
289
OnTest(int test_id)290 void RootWindowViews::OnTest(int test_id) {
291 if (!CURRENTLY_ON_MAIN_THREAD()) {
292 // Execute this method on the main thread.
293 MAIN_POST_CLOSURE(base::BindOnce(&RootWindowViews::OnTest, this, test_id));
294 return;
295 }
296
297 delegate_->OnTest(this, test_id);
298 }
299
OnExit()300 void RootWindowViews::OnExit() {
301 if (!CURRENTLY_ON_MAIN_THREAD()) {
302 // Execute this method on the main thread.
303 MAIN_POST_CLOSURE(base::BindOnce(&RootWindowViews::OnExit, this));
304 return;
305 }
306
307 delegate_->OnExit(this);
308 }
309
OnBrowserCreated(CefRefPtr<CefBrowser> browser)310 void RootWindowViews::OnBrowserCreated(CefRefPtr<CefBrowser> browser) {
311 REQUIRE_MAIN_THREAD();
312 DCHECK(!browser_);
313 browser_ = browser;
314 delegate_->OnBrowserCreated(this, browser);
315 }
316
OnBrowserClosing(CefRefPtr<CefBrowser> browser)317 void RootWindowViews::OnBrowserClosing(CefRefPtr<CefBrowser> browser) {
318 REQUIRE_MAIN_THREAD();
319 // Nothing to do here.
320 }
321
OnBrowserClosed(CefRefPtr<CefBrowser> browser)322 void RootWindowViews::OnBrowserClosed(CefRefPtr<CefBrowser> browser) {
323 REQUIRE_MAIN_THREAD();
324 if (browser_) {
325 DCHECK_EQ(browser->GetIdentifier(), browser_->GetIdentifier());
326 browser_ = nullptr;
327 }
328
329 client_handler_->DetachDelegate();
330 client_handler_ = nullptr;
331
332 browser_destroyed_ = true;
333 NotifyDestroyedIfDone();
334 }
335
OnSetAddress(const std::string & url)336 void RootWindowViews::OnSetAddress(const std::string& url) {
337 if (!CefCurrentlyOn(TID_UI)) {
338 // Execute this method on the UI thread.
339 CefPostTask(TID_UI,
340 base::BindOnce(&RootWindowViews::OnSetAddress, this, url));
341 return;
342 }
343
344 if (window_)
345 window_->SetAddress(url);
346 }
347
OnSetTitle(const std::string & title)348 void RootWindowViews::OnSetTitle(const std::string& title) {
349 if (!CefCurrentlyOn(TID_UI)) {
350 // Execute this method on the UI thread.
351 CefPostTask(TID_UI,
352 base::BindOnce(&RootWindowViews::OnSetTitle, this, title));
353 return;
354 }
355
356 if (window_)
357 window_->SetTitle(title);
358 }
359
OnSetFavicon(CefRefPtr<CefImage> image)360 void RootWindowViews::OnSetFavicon(CefRefPtr<CefImage> image) {
361 if (!CefCurrentlyOn(TID_UI)) {
362 // Execute this method on the UI thread.
363 CefPostTask(TID_UI,
364 base::BindOnce(&RootWindowViews::OnSetFavicon, this, image));
365 return;
366 }
367
368 if (window_)
369 window_->SetFavicon(image);
370 }
371
OnSetFullscreen(bool fullscreen)372 void RootWindowViews::OnSetFullscreen(bool fullscreen) {
373 if (!CefCurrentlyOn(TID_UI)) {
374 // Execute this method on the UI thread.
375 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::OnSetFullscreen, this,
376 fullscreen));
377 return;
378 }
379
380 if (window_)
381 window_->SetFullscreen(fullscreen);
382 }
383
OnAutoResize(const CefSize & new_size)384 void RootWindowViews::OnAutoResize(const CefSize& new_size) {
385 if (!CefCurrentlyOn(TID_UI)) {
386 // Execute this method on the UI thread.
387 CefPostTask(TID_UI,
388 base::BindOnce(&RootWindowViews::OnAutoResize, this, new_size));
389 return;
390 }
391
392 bool has_position = false;
393 CefPoint position;
394 if (position_on_resize_) {
395 // Position the window centered on and immediately below the source.
396 const int x_offset = (initial_bounds_.width - new_size.width) / 2;
397 position.Set(initial_bounds_.x + x_offset,
398 initial_bounds_.y + initial_bounds_.height);
399 has_position = true;
400
401 // Don't change the window position on future resizes.
402 position_on_resize_ = false;
403 }
404
405 if (window_) {
406 window_->SetBrowserSize(new_size, has_position, position);
407 window_->Show();
408 }
409 }
410
OnSetLoadingState(bool isLoading,bool canGoBack,bool canGoForward)411 void RootWindowViews::OnSetLoadingState(bool isLoading,
412 bool canGoBack,
413 bool canGoForward) {
414 if (!CefCurrentlyOn(TID_UI)) {
415 // Execute this method on the UI thread.
416 CefPostTask(TID_UI,
417 base::BindOnce(&RootWindowViews::OnSetLoadingState, this,
418 isLoading, canGoBack, canGoForward));
419 return;
420 }
421
422 if (window_) {
423 window_->SetLoadingState(isLoading, canGoBack, canGoForward);
424
425 if (isLoading) {
426 // Reset to the default window icon when loading begins.
427 window_->SetFavicon(
428 delegate_->GetImageCache()->GetCachedImage("window_icon"));
429 }
430 }
431 }
432
OnSetDraggableRegions(const std::vector<CefDraggableRegion> & regions)433 void RootWindowViews::OnSetDraggableRegions(
434 const std::vector<CefDraggableRegion>& regions) {
435 if (!CefCurrentlyOn(TID_UI)) {
436 // Execute this method on the UI thread.
437 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::OnSetDraggableRegions,
438 this, regions));
439 return;
440 }
441
442 if (window_)
443 window_->SetDraggableRegions(regions);
444 }
445
OnTakeFocus(bool next)446 void RootWindowViews::OnTakeFocus(bool next) {
447 if (!CefCurrentlyOn(TID_UI)) {
448 // Execute this method on the UI thread.
449 CefPostTask(TID_UI,
450 base::BindOnce(&RootWindowViews::OnTakeFocus, this, next));
451 return;
452 }
453
454 if (window_)
455 window_->TakeFocus(next);
456 }
457
OnBeforeContextMenu(CefRefPtr<CefMenuModel> model)458 void RootWindowViews::OnBeforeContextMenu(CefRefPtr<CefMenuModel> model) {
459 CEF_REQUIRE_UI_THREAD();
460 if (window_)
461 window_->OnBeforeContextMenu(model);
462 }
463
CreateClientHandler(const std::string & url)464 void RootWindowViews::CreateClientHandler(const std::string& url) {
465 DCHECK(!client_handler_);
466
467 client_handler_ = new ClientHandlerStd(this, url);
468 client_handler_->set_download_favicon_images(true);
469 }
470
InitOnUIThread(const CefBrowserSettings & settings,const std::string & startup_url,CefRefPtr<CefRequestContext> request_context)471 void RootWindowViews::InitOnUIThread(
472 const CefBrowserSettings& settings,
473 const std::string& startup_url,
474 CefRefPtr<CefRequestContext> request_context) {
475 if (!CefCurrentlyOn(TID_UI)) {
476 // Execute this method on the UI thread.
477 CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::InitOnUIThread, this,
478 settings, startup_url, request_context));
479 return;
480 }
481
482 image_cache_ = delegate_->GetImageCache();
483
484 // Populate the default image cache.
485 ImageCache::ImageInfoSet image_set;
486 for (size_t i = 0U; i < base::size(kDefaultImageCache); ++i)
487 image_set.push_back(ImageCache::ImageInfo::Create2x(kDefaultImageCache[i]));
488
489 image_cache_->LoadImages(
490 image_set, base::BindOnce(&RootWindowViews::CreateViewsWindow, this,
491 settings, startup_url, request_context));
492 }
493
CreateViewsWindow(const CefBrowserSettings & settings,const std::string & startup_url,CefRefPtr<CefRequestContext> request_context,const ImageCache::ImageSet & images)494 void RootWindowViews::CreateViewsWindow(
495 const CefBrowserSettings& settings,
496 const std::string& startup_url,
497 CefRefPtr<CefRequestContext> request_context,
498 const ImageCache::ImageSet& images) {
499 CEF_REQUIRE_UI_THREAD();
500 DCHECK(!window_);
501
502 #ifndef NDEBUG
503 // Make sure the default images loaded successfully.
504 DCHECK_EQ(images.size(), base::size(kDefaultImageCache));
505 for (size_t i = 0U; i < base::size(kDefaultImageCache); ++i) {
506 DCHECK(images[i]) << "Default image " << i << " failed to load";
507 }
508 #endif
509
510 // Create the ViewsWindow. It will show itself after creation.
511 ViewsWindow::Create(this, client_handler_, startup_url, settings,
512 request_context);
513 }
514
NotifyViewsWindowDestroyed()515 void RootWindowViews::NotifyViewsWindowDestroyed() {
516 REQUIRE_MAIN_THREAD();
517 window_destroyed_ = true;
518 NotifyDestroyedIfDone();
519 }
520
NotifyViewsWindowActivated()521 void RootWindowViews::NotifyViewsWindowActivated() {
522 REQUIRE_MAIN_THREAD();
523 delegate_->OnRootWindowActivated(this);
524 }
525
NotifyDestroyedIfDone()526 void RootWindowViews::NotifyDestroyedIfDone() {
527 // Notify once both the window and the browser have been destroyed.
528 if (window_destroyed_ && browser_destroyed_) {
529 delegate_->OnRootWindowDestroyed(this);
530 if (!config_->close_callback.is_null())
531 std::move(config_->close_callback).Run();
532 }
533 }
534
535 } // namespace client
536