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/views_window.h"
6
7 #include <algorithm>
8
9 #include "include/base/cef_build.h"
10 #include "include/base/cef_callback.h"
11 #include "include/cef_app.h"
12 #include "include/cef_i18n_util.h"
13 #include "include/views/cef_box_layout.h"
14 #include "include/wrapper/cef_helpers.h"
15 #include "tests/cefclient/browser/main_context.h"
16 #include "tests/cefclient/browser/resource.h"
17 #include "tests/cefclient/browser/views_style.h"
18 #include "tests/shared/browser/extension_util.h"
19 #include "tests/shared/common/client_switches.h"
20
21 #if !defined(OS_WIN)
22 #define VK_ESCAPE 0x1B
23 #define VK_RETURN 0x0D
24 #define VK_MENU 0x12 // ALT key.
25 #endif
26
27 namespace client {
28
29 namespace {
30
31 const char kDefaultExtensionIcon[] = "window_icon";
32
33 // Control IDs for Views in the top-level Window.
34 enum ControlIds {
35 ID_WINDOW = 1,
36 ID_BROWSER_VIEW,
37 ID_BACK_BUTTON,
38 ID_FORWARD_BUTTON,
39 ID_STOP_BUTTON,
40 ID_RELOAD_BUTTON,
41 ID_URL_TEXTFIELD,
42 ID_MENU_BUTTON,
43
44 // Reserved range of top menu button IDs.
45 ID_TOP_MENU_FIRST,
46 ID_TOP_MENU_LAST = ID_TOP_MENU_FIRST + 10,
47
48 // Reserved range of extension button IDs.
49 ID_EXTENSION_BUTTON_FIRST,
50 ID_EXTENSION_BUTTON_LAST = ID_EXTENSION_BUTTON_FIRST + 10,
51 };
52
53 typedef std::vector<CefRefPtr<CefLabelButton>> LabelButtons;
54
55 // Make all |buttons| the same size.
MakeButtonsSameSize(const LabelButtons & buttons)56 void MakeButtonsSameSize(const LabelButtons& buttons) {
57 CefSize size;
58
59 // Determine the largest button size.
60 for (size_t i = 0U; i < buttons.size(); ++i) {
61 const CefSize& button_size = buttons[i]->GetPreferredSize();
62 if (size.width < button_size.width)
63 size.width = button_size.width;
64 if (size.height < button_size.height)
65 size.height = button_size.height;
66 }
67
68 for (size_t i = 0U; i < buttons.size(); ++i) {
69 // Set the button's minimum size.
70 buttons[i]->SetMinimumSize(size);
71
72 // Re-layout the button and all parent Views.
73 buttons[i]->InvalidateLayout();
74 }
75 }
76
AddTestMenuItems(CefRefPtr<CefMenuModel> test_menu)77 void AddTestMenuItems(CefRefPtr<CefMenuModel> test_menu) {
78 test_menu->AddItem(ID_TESTS_GETSOURCE, "Get Source");
79 test_menu->AddItem(ID_TESTS_GETTEXT, "Get Text");
80 test_menu->AddItem(ID_TESTS_WINDOW_NEW, "New Window");
81 test_menu->AddItem(ID_TESTS_WINDOW_POPUP, "Popup Window");
82 test_menu->AddItem(ID_TESTS_REQUEST, "Request");
83 test_menu->AddItem(ID_TESTS_PLUGIN_INFO, "Plugin Info");
84 test_menu->AddItem(ID_TESTS_ZOOM_IN, "Zoom In");
85 test_menu->AddItem(ID_TESTS_ZOOM_OUT, "Zoom Out");
86 test_menu->AddItem(ID_TESTS_ZOOM_RESET, "Zoom Reset");
87 test_menu->AddItem(ID_TESTS_TRACING_BEGIN, "Begin Tracing");
88 test_menu->AddItem(ID_TESTS_TRACING_END, "End Tracing");
89 test_menu->AddItem(ID_TESTS_PRINT, "Print");
90 test_menu->AddItem(ID_TESTS_PRINT_TO_PDF, "Print to PDF");
91 test_menu->AddItem(ID_TESTS_MUTE_AUDIO, "Mute Audio");
92 test_menu->AddItem(ID_TESTS_UNMUTE_AUDIO, "Unmute Audio");
93 test_menu->AddItem(ID_TESTS_OTHER_TESTS, "Other Tests");
94 }
95
AddFileMenuItems(CefRefPtr<CefMenuModel> file_menu)96 void AddFileMenuItems(CefRefPtr<CefMenuModel> file_menu) {
97 file_menu->AddItem(ID_QUIT, "E&xit");
98
99 // Show the accelerator shortcut text in the menu.
100 file_menu->SetAcceleratorAt(file_menu->GetCount() - 1, 'X', false, false,
101 true);
102 }
103
104 } // namespace
105
106 // static
Create(Delegate * delegate,CefRefPtr<CefClient> client,const CefString & url,const CefBrowserSettings & settings,CefRefPtr<CefRequestContext> request_context)107 CefRefPtr<ViewsWindow> ViewsWindow::Create(
108 Delegate* delegate,
109 CefRefPtr<CefClient> client,
110 const CefString& url,
111 const CefBrowserSettings& settings,
112 CefRefPtr<CefRequestContext> request_context) {
113 CEF_REQUIRE_UI_THREAD();
114 DCHECK(delegate);
115
116 // Create a new ViewsWindow.
117 CefRefPtr<ViewsWindow> views_window = new ViewsWindow(delegate, nullptr);
118
119 // Create a new BrowserView.
120 CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
121 client, url, settings, nullptr, request_context, views_window);
122
123 // Associate the BrowserView with the ViewsWindow.
124 views_window->SetBrowserView(browser_view);
125
126 // Create a new top-level Window. It will show itself after creation.
127 CefWindow::CreateTopLevelWindow(views_window);
128
129 return views_window;
130 }
131
Show()132 void ViewsWindow::Show() {
133 CEF_REQUIRE_UI_THREAD();
134 if (window_)
135 window_->Show();
136 if (browser_view_ && !window_->IsMinimized()) {
137 // Give keyboard focus to the BrowserView.
138 browser_view_->RequestFocus();
139 }
140 }
141
Hide()142 void ViewsWindow::Hide() {
143 CEF_REQUIRE_UI_THREAD();
144 if (window_)
145 window_->Hide();
146 }
147
Minimize()148 void ViewsWindow::Minimize() {
149 CEF_REQUIRE_UI_THREAD();
150 if (window_)
151 window_->Minimize();
152 }
153
Maximize()154 void ViewsWindow::Maximize() {
155 CEF_REQUIRE_UI_THREAD();
156 if (window_)
157 window_->Maximize();
158 }
159
SetBounds(const CefRect & bounds)160 void ViewsWindow::SetBounds(const CefRect& bounds) {
161 CEF_REQUIRE_UI_THREAD();
162 if (window_)
163 window_->SetBounds(bounds);
164 }
165
SetBrowserSize(const CefSize & size,bool has_position,const CefPoint & position)166 void ViewsWindow::SetBrowserSize(const CefSize& size,
167 bool has_position,
168 const CefPoint& position) {
169 CEF_REQUIRE_UI_THREAD();
170 if (browser_view_)
171 browser_view_->SetSize(size);
172 if (window_) {
173 window_->SizeToPreferredSize();
174 if (has_position)
175 window_->SetPosition(position);
176 }
177 }
178
Close(bool force)179 void ViewsWindow::Close(bool force) {
180 CEF_REQUIRE_UI_THREAD();
181 if (!browser_view_)
182 return;
183
184 CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
185 if (browser) {
186 // This will result in a call to CefWindow::Close() which will then call
187 // ViewsWindow::CanClose().
188 browser->GetHost()->CloseBrowser(force);
189 }
190 }
191
SetAddress(const std::string & url)192 void ViewsWindow::SetAddress(const std::string& url) {
193 CEF_REQUIRE_UI_THREAD();
194 if (!window_)
195 return;
196
197 // |location_bar_| may instead be a Chrome toolbar.
198 if (location_bar_ && location_bar_->AsTextfield())
199 location_bar_->AsTextfield()->SetText(url);
200 }
201
SetTitle(const std::string & title)202 void ViewsWindow::SetTitle(const std::string& title) {
203 CEF_REQUIRE_UI_THREAD();
204 if (window_)
205 window_->SetTitle(title);
206 }
207
SetFavicon(CefRefPtr<CefImage> image)208 void ViewsWindow::SetFavicon(CefRefPtr<CefImage> image) {
209 CEF_REQUIRE_UI_THREAD();
210
211 // Window icons should be 16 DIP in size.
212 DCHECK_EQ(std::max(image->GetWidth(), image->GetHeight()), 16U);
213
214 if (window_)
215 window_->SetWindowIcon(image);
216 }
217
SetFullscreen(bool fullscreen)218 void ViewsWindow::SetFullscreen(bool fullscreen) {
219 CEF_REQUIRE_UI_THREAD();
220 if (window_) {
221 // Hide the top controls while in full-screen mode.
222 if (with_controls_)
223 ShowTopControls(!fullscreen);
224
225 window_->SetFullscreen(fullscreen);
226 }
227 }
228
SetAlwaysOnTop(bool on_top)229 void ViewsWindow::SetAlwaysOnTop(bool on_top) {
230 CEF_REQUIRE_UI_THREAD();
231 if (window_) {
232 window_->SetAlwaysOnTop(on_top);
233 }
234 }
235
SetLoadingState(bool isLoading,bool canGoBack,bool canGoForward)236 void ViewsWindow::SetLoadingState(bool isLoading,
237 bool canGoBack,
238 bool canGoForward) {
239 CEF_REQUIRE_UI_THREAD();
240 if (!window_ || chrome_toolbar_type_ == CEF_CTT_NORMAL)
241 return;
242
243 if (with_controls_) {
244 EnableView(ID_BACK_BUTTON, canGoBack);
245 EnableView(ID_FORWARD_BUTTON, canGoForward);
246 EnableView(ID_RELOAD_BUTTON, !isLoading);
247 EnableView(ID_STOP_BUTTON, isLoading);
248 }
249 if (location_bar_) {
250 EnableView(ID_URL_TEXTFIELD, true);
251 }
252 }
253
SetDraggableRegions(const std::vector<CefDraggableRegion> & regions)254 void ViewsWindow::SetDraggableRegions(
255 const std::vector<CefDraggableRegion>& regions) {
256 CEF_REQUIRE_UI_THREAD();
257
258 if (!window_ || !browser_view_)
259 return;
260
261 std::vector<CefDraggableRegion> window_regions;
262
263 // Convert the regions from BrowserView to Window coordinates.
264 std::vector<CefDraggableRegion>::const_iterator it = regions.begin();
265 for (; it != regions.end(); ++it) {
266 CefDraggableRegion region = *it;
267 CefPoint origin = CefPoint(region.bounds.x, region.bounds.y);
268 browser_view_->ConvertPointToWindow(origin);
269 region.bounds.x = origin.x;
270 region.bounds.y = origin.y;
271 window_regions.push_back(region);
272 }
273
274 if (overlay_controls_) {
275 // Exclude all regions obscured by overlays.
276 overlay_controls_->UpdateDraggableRegions(window_regions);
277 }
278
279 window_->SetDraggableRegions(window_regions);
280 }
281
TakeFocus(bool next)282 void ViewsWindow::TakeFocus(bool next) {
283 CEF_REQUIRE_UI_THREAD();
284
285 if (!window_)
286 return;
287
288 if (chrome_toolbar_type_ == CEF_CTT_NORMAL) {
289 top_toolbar_->RequestFocus();
290 } else if (location_bar_) {
291 // Give focus to the location bar.
292 location_bar_->RequestFocus();
293 }
294 }
295
OnBeforeContextMenu(CefRefPtr<CefMenuModel> model)296 void ViewsWindow::OnBeforeContextMenu(CefRefPtr<CefMenuModel> model) {
297 CEF_REQUIRE_UI_THREAD();
298
299 views_style::ApplyTo(model);
300 }
301
OnExtensionsChanged(const ExtensionSet & extensions)302 void ViewsWindow::OnExtensionsChanged(const ExtensionSet& extensions) {
303 CEF_REQUIRE_UI_THREAD();
304
305 if (extensions.empty()) {
306 if (!extensions_.empty()) {
307 extensions_.clear();
308 UpdateExtensionControls();
309 }
310 return;
311 }
312
313 ImageCache::ImageInfoSet image_set;
314
315 ExtensionSet::const_iterator it = extensions.begin();
316 for (; it != extensions.end(); ++it) {
317 CefRefPtr<CefExtension> extension = *it;
318 bool internal = false;
319 const std::string& icon_path =
320 extension_util::GetExtensionIconPath(extension, &internal);
321 if (!icon_path.empty()) {
322 // Load the extension icon.
323 image_set.push_back(
324 ImageCache::ImageInfo::Create1x(icon_path, icon_path, internal));
325 } else {
326 // Get a nullptr image and use the default icon.
327 image_set.push_back(ImageCache::ImageInfo::Empty());
328 }
329 }
330
331 delegate_->GetImageCache()->LoadImages(
332 image_set,
333 base::BindOnce(&ViewsWindow::OnExtensionIconsLoaded, this, extensions));
334 }
335
GetDelegateForPopupBrowserView(CefRefPtr<CefBrowserView> browser_view,const CefBrowserSettings & settings,CefRefPtr<CefClient> client,bool is_devtools)336 CefRefPtr<CefBrowserViewDelegate> ViewsWindow::GetDelegateForPopupBrowserView(
337 CefRefPtr<CefBrowserView> browser_view,
338 const CefBrowserSettings& settings,
339 CefRefPtr<CefClient> client,
340 bool is_devtools) {
341 CEF_REQUIRE_UI_THREAD();
342
343 // The popup browser client is created in CefLifeSpanHandler::OnBeforePopup()
344 // (e.g. via RootWindowViews::InitAsPopup()). The Delegate (RootWindowViews)
345 // knows the association between |client| and itself.
346 Delegate* popup_delegate = delegate_->GetDelegateForPopup(client);
347
348 // Should not be the same RootWindowViews that owns |this|.
349 DCHECK(popup_delegate && popup_delegate != delegate_);
350
351 // Create a new ViewsWindow for the popup BrowserView.
352 return new ViewsWindow(popup_delegate, nullptr);
353 }
354
OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browser_view,CefRefPtr<CefBrowserView> popup_browser_view,bool is_devtools)355 bool ViewsWindow::OnPopupBrowserViewCreated(
356 CefRefPtr<CefBrowserView> browser_view,
357 CefRefPtr<CefBrowserView> popup_browser_view,
358 bool is_devtools) {
359 CEF_REQUIRE_UI_THREAD();
360
361 // Retrieve the ViewsWindow created in GetDelegateForPopupBrowserView.
362 CefRefPtr<ViewsWindow> popup_window =
363 static_cast<ViewsWindow*>(static_cast<CefBrowserViewDelegate*>(
364 popup_browser_view->GetDelegate().get()));
365
366 // Should not be the same ViewsWindow as |this|.
367 DCHECK(popup_window && popup_window != this);
368
369 // Associate the ViewsWindow with the new popup browser.
370 popup_window->SetBrowserView(popup_browser_view);
371
372 // Create a new top-level Window for the popup. It will show itself after
373 // creation.
374 CefWindow::CreateTopLevelWindow(popup_window);
375
376 // We created the Window.
377 return true;
378 }
379
GetChromeToolbarType()380 CefBrowserViewDelegate::ChromeToolbarType ViewsWindow::GetChromeToolbarType() {
381 return chrome_toolbar_type_;
382 }
383
OnButtonPressed(CefRefPtr<CefButton> button)384 void ViewsWindow::OnButtonPressed(CefRefPtr<CefButton> button) {
385 CEF_REQUIRE_UI_THREAD();
386 DCHECK(with_controls_);
387
388 if (!browser_view_)
389 return;
390
391 CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
392 if (!browser)
393 return;
394
395 switch (button->GetID()) {
396 case ID_BACK_BUTTON:
397 browser->GoBack();
398 break;
399 case ID_FORWARD_BUTTON:
400 browser->GoForward();
401 break;
402 case ID_STOP_BUTTON:
403 browser->StopLoad();
404 break;
405 case ID_RELOAD_BUTTON:
406 browser->Reload();
407 break;
408 case ID_MENU_BUTTON:
409 break;
410 default:
411 NOTREACHED();
412 break;
413 }
414 }
415
OnMenuButtonPressed(CefRefPtr<CefMenuButton> menu_button,const CefPoint & screen_point,CefRefPtr<CefMenuButtonPressedLock> button_pressed_lock)416 void ViewsWindow::OnMenuButtonPressed(
417 CefRefPtr<CefMenuButton> menu_button,
418 const CefPoint& screen_point,
419 CefRefPtr<CefMenuButtonPressedLock> button_pressed_lock) {
420 CEF_REQUIRE_UI_THREAD();
421
422 const int id = menu_button->GetID();
423 if (id >= ID_EXTENSION_BUTTON_FIRST && id <= ID_EXTENSION_BUTTON_LAST) {
424 const size_t extension_idx = id - ID_EXTENSION_BUTTON_FIRST;
425 if (extension_idx >= extensions_.size()) {
426 LOG(ERROR) << "Invalid extension index " << extension_idx;
427 return;
428 }
429
430 // Keep the button pressed until the extension window is closed.
431 extension_button_pressed_lock_ = button_pressed_lock;
432
433 // Create a window for the extension.
434 delegate_->CreateExtensionWindow(
435 extensions_[extension_idx].extension_, menu_button->GetBoundsInScreen(),
436 window_, base::BindOnce(&ViewsWindow::OnExtensionWindowClosed, this));
437 return;
438 }
439
440 DCHECK(with_controls_ || with_overlay_controls_);
441 DCHECK_EQ(ID_MENU_BUTTON, id);
442
443 auto point = screen_point;
444 if (with_overlay_controls_) {
445 // Align the menu correctly under the button.
446 const int button_width = menu_button->GetSize().width;
447 if (CefIsRTL()) {
448 point.x += button_width - 4;
449 } else {
450 point.x -= button_width - 4;
451 }
452 }
453
454 menu_button->ShowMenu(button_menu_model_, point,
455 with_overlay_controls_ ? CEF_MENU_ANCHOR_TOPLEFT
456 : CEF_MENU_ANCHOR_TOPRIGHT);
457 }
458
ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,int command_id,cef_event_flags_t event_flags)459 void ViewsWindow::ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
460 int command_id,
461 cef_event_flags_t event_flags) {
462 CEF_REQUIRE_UI_THREAD();
463 DCHECK(with_controls_ || with_overlay_controls_);
464
465 if (command_id == ID_QUIT) {
466 delegate_->OnExit();
467 } else if (command_id >= ID_TESTS_FIRST && command_id <= ID_TESTS_LAST) {
468 delegate_->OnTest(command_id);
469 } else {
470 NOTREACHED();
471 }
472 }
473
OnKeyEvent(CefRefPtr<CefTextfield> textfield,const CefKeyEvent & event)474 bool ViewsWindow::OnKeyEvent(CefRefPtr<CefTextfield> textfield,
475 const CefKeyEvent& event) {
476 CEF_REQUIRE_UI_THREAD();
477 DCHECK_EQ(ID_URL_TEXTFIELD, textfield->GetID());
478
479 // Trigger when the return key is pressed.
480 if (window_ && browser_view_ && event.type == KEYEVENT_RAWKEYDOWN &&
481 event.windows_key_code == VK_RETURN) {
482 CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
483 if (browser) {
484 const CefString& url = textfield->GetText();
485 if (!url.empty())
486 browser->GetMainFrame()->LoadURL(url);
487 }
488
489 // We handled the event.
490 return true;
491 }
492
493 return false;
494 }
495
OnWindowCreated(CefRefPtr<CefWindow> window)496 void ViewsWindow::OnWindowCreated(CefRefPtr<CefWindow> window) {
497 CEF_REQUIRE_UI_THREAD();
498 DCHECK(browser_view_);
499 DCHECK(!window_);
500 DCHECK(window);
501
502 window_ = window;
503 window_->SetID(ID_WINDOW);
504
505 with_controls_ = delegate_->WithControls();
506
507 delegate_->OnViewsWindowCreated(this);
508
509 const CefRect bounds = GetInitialBounds();
510 if (bounds.x == 0 && bounds.y == 0) {
511 // Size the Window and center it.
512 window_->CenterWindow(CefSize(bounds.width, bounds.height));
513 } else {
514 // Set the Window bounds as specified.
515 window_->SetBounds(bounds);
516 }
517
518 // Set the background color for regions that are not obscured by other Views.
519 views_style::ApplyTo(window_.get());
520
521 if (with_controls_ || with_overlay_controls_) {
522 // Create the MenuModel that will be displayed via the menu button.
523 CreateMenuModel();
524 }
525
526 if (with_controls_) {
527 // Add the BrowserView and other controls to the Window.
528 AddBrowserView();
529
530 // Add keyboard accelerators to the Window.
531 AddAccelerators();
532
533 // Hide the top controls while in full-screen mode.
534 if (initial_show_state_ == CEF_SHOW_STATE_FULLSCREEN) {
535 ShowTopControls(false);
536 }
537 } else {
538 // Add the BrowserView as the only child of the Window.
539 window_->AddChildView(browser_view_);
540
541 if (!delegate_->WithExtension()) {
542 // Choose a reasonable minimum window size.
543 minimum_window_size_ = CefSize(100, 100);
544 }
545 }
546
547 if (!delegate_->InitiallyHidden()) {
548 // Show the Window.
549 Show();
550 }
551 }
552
OnWindowDestroyed(CefRefPtr<CefWindow> window)553 void ViewsWindow::OnWindowDestroyed(CefRefPtr<CefWindow> window) {
554 CEF_REQUIRE_UI_THREAD();
555 DCHECK(window_);
556
557 delegate_->OnViewsWindowDestroyed(this);
558
559 browser_view_ = nullptr;
560 button_menu_model_ = nullptr;
561 if (top_menu_bar_) {
562 top_menu_bar_->Reset();
563 top_menu_bar_ = nullptr;
564 }
565 extensions_panel_ = nullptr;
566 menu_button_ = nullptr;
567 window_ = nullptr;
568 }
569
CanClose(CefRefPtr<CefWindow> window)570 bool ViewsWindow::CanClose(CefRefPtr<CefWindow> window) {
571 CEF_REQUIRE_UI_THREAD();
572
573 // Allow the window to close if the browser says it's OK.
574 CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
575 if (browser)
576 return browser->GetHost()->TryCloseBrowser();
577 return true;
578 }
579
GetParentWindow(CefRefPtr<CefWindow> window,bool * is_menu,bool * can_activate_menu)580 CefRefPtr<CefWindow> ViewsWindow::GetParentWindow(CefRefPtr<CefWindow> window,
581 bool* is_menu,
582 bool* can_activate_menu) {
583 CEF_REQUIRE_UI_THREAD();
584 CefRefPtr<CefWindow> parent_window = delegate_->GetParentWindow();
585 if (parent_window) {
586 // Should be an extension window, in which case we want it to behave as a
587 // menu and allow activation.
588 DCHECK(delegate_->WithExtension());
589 *is_menu = true;
590 *can_activate_menu = true;
591 }
592 return parent_window;
593 }
594
GetInitialBounds(CefRefPtr<CefWindow> window)595 CefRect ViewsWindow::GetInitialBounds(CefRefPtr<CefWindow> window) {
596 CEF_REQUIRE_UI_THREAD();
597 if (frameless_) {
598 // Need to provide a size for frameless windows that will be centered.
599 const CefRect bounds = GetInitialBounds();
600 if (bounds.x == 0 && bounds.y == 0) {
601 return bounds;
602 }
603 }
604 return CefRect();
605 }
606
GetInitialShowState(CefRefPtr<CefWindow> window)607 cef_show_state_t ViewsWindow::GetInitialShowState(CefRefPtr<CefWindow> window) {
608 CEF_REQUIRE_UI_THREAD();
609 return initial_show_state_;
610 }
611
IsFrameless(CefRefPtr<CefWindow> window)612 bool ViewsWindow::IsFrameless(CefRefPtr<CefWindow> window) {
613 CEF_REQUIRE_UI_THREAD();
614 return frameless_;
615 }
616
CanResize(CefRefPtr<CefWindow> window)617 bool ViewsWindow::CanResize(CefRefPtr<CefWindow> window) {
618 CEF_REQUIRE_UI_THREAD();
619 // Don't allow windows hosting extensions to resize.
620 return !delegate_->WithExtension();
621 }
622
OnAccelerator(CefRefPtr<CefWindow> window,int command_id)623 bool ViewsWindow::OnAccelerator(CefRefPtr<CefWindow> window, int command_id) {
624 CEF_REQUIRE_UI_THREAD();
625
626 if (command_id == ID_QUIT) {
627 delegate_->OnExit();
628 return true;
629 }
630
631 return false;
632 }
633
OnKeyEvent(CefRefPtr<CefWindow> window,const CefKeyEvent & event)634 bool ViewsWindow::OnKeyEvent(CefRefPtr<CefWindow> window,
635 const CefKeyEvent& event) {
636 CEF_REQUIRE_UI_THREAD();
637
638 if (!window_)
639 return false;
640
641 if (delegate_->WithExtension() && event.type == KEYEVENT_RAWKEYDOWN &&
642 event.windows_key_code == VK_ESCAPE) {
643 // Close the extension window on escape.
644 Close(false);
645 return true;
646 }
647
648 if (!with_controls_)
649 return false;
650
651 if (event.type == KEYEVENT_RAWKEYDOWN && event.windows_key_code == VK_MENU) {
652 // ALT key is pressed.
653 int last_focused_view = last_focused_view_;
654 bool menu_had_focus = menu_has_focus_;
655
656 // Toggle menu button focusable.
657 SetMenuFocusable(!menu_has_focus_);
658
659 if (menu_had_focus && last_focused_view != 0) {
660 // Restore focus to the view that was previously focused.
661 window_->GetViewForID(last_focused_view)->RequestFocus();
662 }
663
664 return true;
665 }
666
667 if (menu_has_focus_ && top_menu_bar_)
668 return top_menu_bar_->OnKeyEvent(event);
669
670 return false;
671 }
672
GetMinimumSize(CefRefPtr<CefView> view)673 CefSize ViewsWindow::GetMinimumSize(CefRefPtr<CefView> view) {
674 CEF_REQUIRE_UI_THREAD();
675
676 if (view->GetID() == ID_WINDOW)
677 return minimum_window_size_;
678
679 return CefSize();
680 }
681
OnFocus(CefRefPtr<CefView> view)682 void ViewsWindow::OnFocus(CefRefPtr<CefView> view) {
683 CEF_REQUIRE_UI_THREAD();
684
685 const int view_id = view->GetID();
686
687 // Keep track of the non-menu view that was last focused.
688 if (last_focused_view_ != view_id &&
689 (!top_menu_bar_ || !top_menu_bar_->HasMenuId(view_id))) {
690 last_focused_view_ = view_id;
691 }
692
693 // When focus leaves the menu buttons make them unfocusable.
694 if (menu_has_focus_) {
695 if (top_menu_bar_) {
696 if (!top_menu_bar_->HasMenuId(view_id))
697 SetMenuFocusable(false);
698 } else if (view_id != ID_MENU_BUTTON) {
699 SetMenuFocusable(false);
700 }
701 }
702
703 if (view_id == ID_BROWSER_VIEW)
704 delegate_->OnViewsWindowActivated(this);
705 }
706
OnBlur(CefRefPtr<CefView> view)707 void ViewsWindow::OnBlur(CefRefPtr<CefView> view) {
708 CEF_REQUIRE_UI_THREAD();
709
710 const int view_id = view->GetID();
711 if (view_id == ID_BROWSER_VIEW && delegate_->WithExtension()) {
712 // Close windows hosting extensions when the browser loses focus.
713 Close(false);
714 }
715 }
716
OnWindowChanged(CefRefPtr<CefView> view,bool added)717 void ViewsWindow::OnWindowChanged(CefRefPtr<CefView> view, bool added) {
718 const int view_id = view->GetID();
719 if (view_id != ID_BROWSER_VIEW)
720 return;
721
722 if (added) {
723 if (with_controls_) {
724 AddControls();
725 }
726
727 if (with_overlay_controls_) {
728 overlay_controls_ = new ViewsOverlayControls();
729 overlay_controls_->Initialize(window_, CreateMenuButton(),
730 CreateLocationBar(),
731 chrome_toolbar_type_ != CEF_CTT_NONE);
732 }
733 } else {
734 if (overlay_controls_) {
735 // Overlay controls may include the Chrome toolbar, in which case they
736 // need to be removed before the BrowserView.
737 overlay_controls_->Destroy();
738 overlay_controls_ = nullptr;
739 location_bar_ = nullptr;
740 }
741 }
742 }
743
OnLayoutChanged(CefRefPtr<CefView> view,const CefRect & new_bounds)744 void ViewsWindow::OnLayoutChanged(CefRefPtr<CefView> view,
745 const CefRect& new_bounds) {
746 const int view_id = view->GetID();
747 if (view_id != ID_BROWSER_VIEW)
748 return;
749
750 if (overlay_controls_) {
751 overlay_controls_->UpdateControls();
752 }
753 }
754
MenuBarExecuteCommand(CefRefPtr<CefMenuModel> menu_model,int command_id,cef_event_flags_t event_flags)755 void ViewsWindow::MenuBarExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
756 int command_id,
757 cef_event_flags_t event_flags) {
758 ExecuteCommand(menu_model, command_id, event_flags);
759 }
760
ViewsWindow(Delegate * delegate,CefRefPtr<CefBrowserView> browser_view)761 ViewsWindow::ViewsWindow(Delegate* delegate,
762 CefRefPtr<CefBrowserView> browser_view)
763 : delegate_(delegate),
764 with_controls_(false),
765 menu_has_focus_(false),
766 last_focused_view_(false) {
767 DCHECK(delegate_);
768 if (browser_view)
769 SetBrowserView(browser_view);
770
771 CefRefPtr<CefCommandLine> command_line =
772 CefCommandLine::GetGlobalCommandLine();
773
774 const bool hide_frame = command_line->HasSwitch(switches::kHideFrame);
775 const bool hide_overlays = command_line->HasSwitch(switches::kHideOverlays);
776
777 // Without a window frame.
778 frameless_ = hide_frame || delegate_->WithExtension();
779
780 // With an overlay that mimics window controls.
781 with_overlay_controls_ =
782 hide_frame && !hide_overlays && !delegate_->WithControls();
783
784 if (MainContext::Get()->UseChromeRuntime()) {
785 const std::string& toolbar_type =
786 command_line->GetSwitchValue(switches::kShowChromeToolbar);
787 if (toolbar_type == "none") {
788 chrome_toolbar_type_ = CEF_CTT_NONE;
789 } else if (toolbar_type == "location") {
790 chrome_toolbar_type_ = CEF_CTT_LOCATION;
791 } else {
792 chrome_toolbar_type_ =
793 with_overlay_controls_ ? CEF_CTT_LOCATION : CEF_CTT_NORMAL;
794 }
795 } else {
796 chrome_toolbar_type_ = CEF_CTT_NONE;
797 }
798
799 const std::string& show_state =
800 command_line->GetSwitchValue(switches::kInitialShowState);
801 if (show_state == "minimized") {
802 initial_show_state_ = CEF_SHOW_STATE_MINIMIZED;
803 } else if (show_state == "maximized") {
804 initial_show_state_ = CEF_SHOW_STATE_MAXIMIZED;
805 } else if (show_state == "fullscreen") {
806 initial_show_state_ = CEF_SHOW_STATE_FULLSCREEN;
807 }
808
809 #if !defined(OS_MAC)
810 // On Mac we don't show a top menu on the window. The options are available in
811 // the app menu instead.
812 if (!command_line->HasSwitch(switches::kHideTopMenu)) {
813 top_menu_bar_ = new ViewsMenuBar(this, ID_TOP_MENU_FIRST);
814 }
815 #endif
816 }
817
SetBrowserView(CefRefPtr<CefBrowserView> browser_view)818 void ViewsWindow::SetBrowserView(CefRefPtr<CefBrowserView> browser_view) {
819 DCHECK(!browser_view_);
820 DCHECK(browser_view);
821 DCHECK(browser_view->IsValid());
822 DCHECK(!browser_view->IsAttached());
823 browser_view_ = browser_view;
824 browser_view_->SetID(ID_BROWSER_VIEW);
825 }
826
CreateMenuModel()827 void ViewsWindow::CreateMenuModel() {
828 // Create the menu button model.
829 button_menu_model_ = CefMenuModel::CreateMenuModel(this);
830 CefRefPtr<CefMenuModel> test_menu =
831 button_menu_model_->AddSubMenu(0, "&Tests");
832 views_style::ApplyTo(button_menu_model_);
833 AddTestMenuItems(test_menu);
834 AddFileMenuItems(button_menu_model_);
835
836 if (top_menu_bar_) {
837 // Add the menus to the top menu bar.
838 AddFileMenuItems(top_menu_bar_->CreateMenuModel("&File", nullptr));
839 AddTestMenuItems(top_menu_bar_->CreateMenuModel("&Tests", nullptr));
840 }
841 }
842
CreateBrowseButton(const std::string & label,int id)843 CefRefPtr<CefLabelButton> ViewsWindow::CreateBrowseButton(
844 const std::string& label,
845 int id) {
846 CefRefPtr<CefLabelButton> button =
847 CefLabelButton::CreateLabelButton(this, label);
848 button->SetID(id);
849 views_style::ApplyTo(button.get());
850 button->SetInkDropEnabled(true);
851 button->SetEnabled(false); // Disabled by default.
852 button->SetFocusable(false); // Don't give focus to the button.
853
854 return button;
855 }
856
CreateMenuButton()857 CefRefPtr<CefMenuButton> ViewsWindow::CreateMenuButton() {
858 // Create the menu button.
859 DCHECK(!menu_button_);
860 menu_button_ = CefMenuButton::CreateMenuButton(this, CefString());
861 menu_button_->SetID(ID_MENU_BUTTON);
862 menu_button_->SetImage(
863 CEF_BUTTON_STATE_NORMAL,
864 delegate_->GetImageCache()->GetCachedImage("menu_icon"));
865 views_style::ApplyTo(menu_button_.get());
866 menu_button_->SetInkDropEnabled(true);
867 // Override the default minimum size.
868 menu_button_->SetMinimumSize(CefSize(0, 0));
869 return menu_button_;
870 }
871
CreateLocationBar()872 CefRefPtr<CefView> ViewsWindow::CreateLocationBar() {
873 DCHECK(!location_bar_);
874 if (chrome_toolbar_type_ == CEF_CTT_LOCATION) {
875 // Chrome will provide a minimal location bar.
876 location_bar_ = browser_view_->GetChromeToolbar();
877 DCHECK(location_bar_);
878 views_style::ApplyBackgroundTo(location_bar_);
879 }
880 if (!location_bar_) {
881 // Create the URL textfield.
882 CefRefPtr<CefTextfield> url_textfield = CefTextfield::CreateTextfield(this);
883 url_textfield->SetID(ID_URL_TEXTFIELD);
884 url_textfield->SetEnabled(false); // Disabled by default.
885 views_style::ApplyTo(url_textfield);
886 location_bar_ = url_textfield;
887 }
888 return location_bar_;
889 }
890
AddBrowserView()891 void ViewsWindow::AddBrowserView() {
892 // Use a vertical box layout for |window|.
893 CefBoxLayoutSettings window_layout_settings;
894 window_layout_settings.horizontal = false;
895 window_layout_settings.between_child_spacing = 2;
896 CefRefPtr<CefBoxLayout> window_layout =
897 window_->SetToBoxLayout(window_layout_settings);
898
899 window_->AddChildView(browser_view_);
900
901 // Allow |browser_view_| to grow and fill any remaining space.
902 window_layout->SetFlexForView(browser_view_, 1);
903
904 // Remaining setup will be performed in OnWindowChanged after the BrowserView
905 // is added to the CefWindow. This is necessary because Chrome toolbars are
906 // only available after the BrowserView is added.
907 }
908
AddControls()909 void ViewsWindow::AddControls() {
910 // Build the remainder of the UI now that the BrowserView has been added to
911 // the CefWindow. This is a requirement to use Chrome toolbars.
912
913 CefRefPtr<CefPanel> top_menu_panel;
914 if (top_menu_bar_)
915 top_menu_panel = top_menu_bar_->GetMenuPanel();
916
917 LabelButtons browse_buttons;
918
919 if (chrome_toolbar_type_ == CEF_CTT_NORMAL) {
920 // Chrome will provide a normal toolbar with location, menu, etc.
921 top_toolbar_ = browser_view_->GetChromeToolbar();
922 DCHECK(top_toolbar_);
923 }
924
925 if (!top_toolbar_) {
926 // Create the browse buttons.
927 browse_buttons.push_back(CreateBrowseButton("Back", ID_BACK_BUTTON));
928 browse_buttons.push_back(CreateBrowseButton("Forward", ID_FORWARD_BUTTON));
929 browse_buttons.push_back(CreateBrowseButton("Reload", ID_RELOAD_BUTTON));
930 browse_buttons.push_back(CreateBrowseButton("Stop", ID_STOP_BUTTON));
931
932 CreateLocationBar();
933 CreateMenuButton();
934
935 // Create the top panel.
936 CefRefPtr<CefPanel> top_panel = CefPanel::CreatePanel(nullptr);
937
938 // Use a horizontal box layout for |top_panel|.
939 CefBoxLayoutSettings top_panel_layout_settings;
940 top_panel_layout_settings.horizontal = true;
941 CefRefPtr<CefBoxLayout> top_panel_layout =
942 top_panel->SetToBoxLayout(top_panel_layout_settings);
943
944 // Add the buttons and URL textfield to |top_panel|.
945 for (size_t i = 0U; i < browse_buttons.size(); ++i)
946 top_panel->AddChildView(browse_buttons[i]);
947 top_panel->AddChildView(location_bar_);
948
949 UpdateExtensionControls();
950 DCHECK(extensions_panel_);
951 top_panel->AddChildView(extensions_panel_);
952
953 top_panel->AddChildView(menu_button_);
954 views_style::ApplyTo(top_panel);
955
956 // Allow |location| to grow and fill any remaining space.
957 top_panel_layout->SetFlexForView(location_bar_, 1);
958
959 top_toolbar_ = top_panel;
960 }
961
962 // Add the top panel and browser view to |window|.
963 int top_index = 0;
964 if (top_menu_panel)
965 window_->AddChildViewAt(top_menu_panel, top_index++);
966 window_->AddChildViewAt(top_toolbar_, top_index);
967
968 // Lay out |window| so we can get the default button sizes.
969 window_->Layout();
970
971 int min_width = 200;
972 if (!browse_buttons.empty()) {
973 // Make all browse buttons the same size.
974 MakeButtonsSameSize(browse_buttons);
975
976 // Lay out |window| again with the new button sizes.
977 window_->Layout();
978
979 // Minimum window width is the size of all buttons plus some extra.
980 min_width = browse_buttons[0]->GetBounds().width * 4 +
981 menu_button_->GetBounds().width + 100;
982 }
983
984 // Minimum window height is the hight of the top toolbar plus some extra.
985 int min_height = top_toolbar_->GetBounds().height + 100;
986 if (top_menu_panel)
987 min_height += top_menu_panel->GetBounds().height;
988
989 minimum_window_size_ = CefSize(min_width, min_height);
990 }
991
AddAccelerators()992 void ViewsWindow::AddAccelerators() {
993 // Trigger accelerators without first forwarding to web content.
994 browser_view_->SetPreferAccelerators(true);
995
996 // Specify the accelerators to handle. OnAccelerator will be called when the
997 // accelerator is triggered.
998 window_->SetAccelerator(ID_QUIT, 'X', false, false, true);
999 }
1000
SetMenuFocusable(bool focusable)1001 void ViewsWindow::SetMenuFocusable(bool focusable) {
1002 if (!window_ || !with_controls_)
1003 return;
1004
1005 if (top_menu_bar_) {
1006 top_menu_bar_->SetMenuFocusable(focusable);
1007 } else {
1008 window_->GetViewForID(ID_MENU_BUTTON)->SetFocusable(focusable);
1009
1010 if (focusable) {
1011 // Give focus to menu button.
1012 window_->GetViewForID(ID_MENU_BUTTON)->RequestFocus();
1013 }
1014 }
1015
1016 menu_has_focus_ = focusable;
1017 }
1018
EnableView(int id,bool enable)1019 void ViewsWindow::EnableView(int id, bool enable) {
1020 if (!window_)
1021 return;
1022 // Special handling for |location_bar_| which may be an overlay (e.g. not a
1023 // child of this view).
1024 CefRefPtr<CefView> view =
1025 id == ID_URL_TEXTFIELD ? location_bar_ : window_->GetViewForID(id);
1026 if (view)
1027 view->SetEnabled(enable);
1028 }
1029
ShowTopControls(bool show)1030 void ViewsWindow::ShowTopControls(bool show) {
1031 if (!window_ || !with_controls_)
1032 return;
1033
1034 // Change the visibility of the top toolbar.
1035 if (top_toolbar_->IsVisible() != show) {
1036 top_toolbar_->SetVisible(show);
1037 top_toolbar_->InvalidateLayout();
1038 }
1039 }
1040
UpdateExtensionControls()1041 void ViewsWindow::UpdateExtensionControls() {
1042 CEF_REQUIRE_UI_THREAD();
1043
1044 if (!window_ || !with_controls_)
1045 return;
1046
1047 if (!extensions_panel_) {
1048 extensions_panel_ = CefPanel::CreatePanel(nullptr);
1049
1050 // Use a horizontal box layout for |top_panel|.
1051 CefBoxLayoutSettings top_panel_layout_settings;
1052 top_panel_layout_settings.horizontal = true;
1053 CefRefPtr<CefBoxLayout> top_panel_layout =
1054 extensions_panel_->SetToBoxLayout(top_panel_layout_settings);
1055 } else {
1056 extensions_panel_->RemoveAllChildViews();
1057 }
1058
1059 if (extensions_.size() >
1060 ID_EXTENSION_BUTTON_LAST - ID_EXTENSION_BUTTON_FIRST) {
1061 LOG(WARNING) << "Too many extensions loaded. Some will be ignored.";
1062 }
1063
1064 ExtensionInfoSet::const_iterator it = extensions_.begin();
1065 for (int id = ID_EXTENSION_BUTTON_FIRST;
1066 it != extensions_.end() && id <= ID_EXTENSION_BUTTON_LAST; ++id, ++it) {
1067 CefRefPtr<CefMenuButton> button =
1068 CefMenuButton::CreateMenuButton(this, CefString());
1069 button->SetID(id);
1070 button->SetImage(CEF_BUTTON_STATE_NORMAL, (*it).image_);
1071 views_style::ApplyTo(button.get());
1072 button->SetInkDropEnabled(true);
1073 // Override the default minimum size.
1074 button->SetMinimumSize(CefSize(0, 0));
1075
1076 extensions_panel_->AddChildView(button);
1077 }
1078
1079 CefRefPtr<CefView> parent_view = extensions_panel_->GetParentView();
1080 if (parent_view)
1081 parent_view->InvalidateLayout();
1082 }
1083
OnExtensionIconsLoaded(const ExtensionSet & extensions,const ImageCache::ImageSet & images)1084 void ViewsWindow::OnExtensionIconsLoaded(const ExtensionSet& extensions,
1085 const ImageCache::ImageSet& images) {
1086 if (!CefCurrentlyOn(TID_UI)) {
1087 // Execute this method on the UI thread.
1088 CefPostTask(TID_UI, base::BindOnce(&ViewsWindow::OnExtensionIconsLoaded,
1089 this, extensions, images));
1090 return;
1091 }
1092
1093 DCHECK_EQ(extensions.size(), images.size());
1094
1095 extensions_.clear();
1096
1097 ExtensionSet::const_iterator it1 = extensions.begin();
1098 ImageCache::ImageSet::const_iterator it2 = images.begin();
1099 for (; it1 != extensions.end() && it2 != images.end(); ++it1, ++it2) {
1100 CefRefPtr<CefImage> icon = *it2;
1101 if (!icon)
1102 icon = delegate_->GetImageCache()->GetCachedImage(kDefaultExtensionIcon);
1103 extensions_.push_back(ExtensionInfo(*it1, icon));
1104 }
1105
1106 UpdateExtensionControls();
1107 }
1108
OnExtensionWindowClosed()1109 void ViewsWindow::OnExtensionWindowClosed() {
1110 if (!CefCurrentlyOn(TID_UI)) {
1111 // Execute this method on the UI thread.
1112 CefPostTask(TID_UI,
1113 base::BindOnce(&ViewsWindow::OnExtensionWindowClosed, this));
1114 return;
1115 }
1116
1117 // Restore the button state.
1118 extension_button_pressed_lock_ = nullptr;
1119 }
1120
GetInitialBounds() const1121 CefRect ViewsWindow::GetInitialBounds() const {
1122 CEF_REQUIRE_UI_THREAD();
1123 CefRect bounds = delegate_->GetWindowBounds();
1124 if (bounds.IsEmpty()) {
1125 // Use the default size.
1126 bounds.width = 800;
1127 bounds.height = 600;
1128 }
1129 return bounds;
1130 }
1131
1132 } // namespace client
1133