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