// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. #include "tests/cefclient/browser/views_window.h" #include #include "include/base/cef_bind.h" #include "include/base/cef_build.h" #include "include/cef_app.h" #include "include/views/cef_box_layout.h" #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/resource.h" #include "tests/cefclient/browser/views_style.h" #include "tests/shared/browser/resource_util.h" #include "tests/shared/common/client_switches.h" #if !defined(OS_WIN) #define VK_RETURN 0x0D #define VK_MENU 0x12 // ALT key. #endif namespace client { namespace { // Control IDs for Views in the top-level Window. enum ControlIds { ID_WINDOW = 1, ID_BROWSER_VIEW, ID_BACK_BUTTON, ID_FORWARD_BUTTON, ID_STOP_BUTTON, ID_RELOAD_BUTTON, ID_URL_TEXTFIELD, ID_MENU_BUTTON, // Reserved range of top menu button IDs. ID_TOP_MENU_FIRST, ID_TOP_MENU_LAST = ID_TOP_MENU_FIRST + 10, }; typedef std::vector> LabelButtons; // Make all |buttons| the same size. void MakeButtonsSameSize(const LabelButtons& buttons) { CefSize size; // Determine the largest button size. for (size_t i = 0U; i < buttons.size(); ++i) { const CefSize& button_size = buttons[i]->GetPreferredSize(); if (size.width < button_size.width) size.width = button_size.width; if (size.height < button_size.height) size.height = button_size.height; } for (size_t i = 0U; i < buttons.size(); ++i) { // Set the button's minimum size. buttons[i]->SetMinimumSize(size); // Re-layout the button and all parent Views. buttons[i]->InvalidateLayout(); } } void AddTestMenuItems(CefRefPtr test_menu) { test_menu->AddItem(ID_TESTS_GETSOURCE, "Get Source"); test_menu->AddItem(ID_TESTS_GETTEXT, "Get Text"); test_menu->AddItem(ID_TESTS_WINDOW_NEW, "New Window"); test_menu->AddItem(ID_TESTS_WINDOW_POPUP, "Popup Window"); test_menu->AddItem(ID_TESTS_REQUEST, "Request"); test_menu->AddItem(ID_TESTS_PLUGIN_INFO, "Plugin Info"); test_menu->AddItem(ID_TESTS_ZOOM_IN, "Zoom In"); test_menu->AddItem(ID_TESTS_ZOOM_OUT, "Zoom Out"); test_menu->AddItem(ID_TESTS_ZOOM_RESET, "Zoom Reset"); test_menu->AddItem(ID_TESTS_TRACING_BEGIN, "Begin Tracing"); test_menu->AddItem(ID_TESTS_TRACING_END, "End Tracing"); test_menu->AddItem(ID_TESTS_PRINT, "Print"); test_menu->AddItem(ID_TESTS_PRINT_TO_PDF, "Print to PDF"); test_menu->AddItem(ID_TESTS_OTHER_TESTS, "Other Tests"); } void AddFileMenuItems(CefRefPtr file_menu) { file_menu->AddItem(ID_QUIT, "E&xit"); // Show the accelerator shortcut text in the menu. file_menu->SetAcceleratorAt(file_menu->GetCount() - 1, 'X', false, false, true); } } // namespace // static CefRefPtr ViewsWindow::Create( Delegate* delegate, CefRefPtr client, const CefString& url, const CefBrowserSettings& settings, CefRefPtr request_context) { CEF_REQUIRE_UI_THREAD(); DCHECK(delegate); // Create a new ViewsWindow. CefRefPtr views_window = new ViewsWindow(delegate, NULL); // Create a new BrowserView. CefRefPtr browser_view = CefBrowserView::CreateBrowserView( client, url, settings, request_context, views_window); // Associate the BrowserView with the ViewsWindow. views_window->SetBrowserView(browser_view); // Create a new top-level Window. It will show itself after creation. CefWindow::CreateTopLevelWindow(views_window); return views_window; } void ViewsWindow::Show() { CEF_REQUIRE_UI_THREAD(); if (window_) window_->Show(); } void ViewsWindow::Hide() { CEF_REQUIRE_UI_THREAD(); if (window_) window_->Hide(); } void ViewsWindow::Minimize() { CEF_REQUIRE_UI_THREAD(); if (window_) window_->Minimize(); } void ViewsWindow::Maximize() { CEF_REQUIRE_UI_THREAD(); if (window_) window_->Maximize(); } void ViewsWindow::SetBounds(const CefRect& bounds) { CEF_REQUIRE_UI_THREAD(); if (window_) window_->SetBounds(bounds); } void ViewsWindow::Close(bool force) { CEF_REQUIRE_UI_THREAD(); if (!browser_view_) return; CefRefPtr browser = browser_view_->GetBrowser(); if (browser) { // This will result in a call to CefWindow::Close() which will then call // ViewsWindow::CanClose(). browser->GetHost()->CloseBrowser(force); } } void ViewsWindow::SetAddress(const std::string& url) { CEF_REQUIRE_UI_THREAD(); if (!window_ || !with_controls_) return; CefRefPtr view = window_->GetViewForID(ID_URL_TEXTFIELD); if (view && view->AsTextfield()) view->AsTextfield()->SetText(url); } void ViewsWindow::SetTitle(const std::string& title) { CEF_REQUIRE_UI_THREAD(); if (window_) window_->SetTitle(title); } void ViewsWindow::SetFavicon(CefRefPtr image) { CEF_REQUIRE_UI_THREAD(); // Window icons should be 16 DIP in size. DCHECK_EQ(std::max(image->GetWidth(), image->GetHeight()), 16U); if (window_) window_->SetWindowIcon(image); } void ViewsWindow::SetFullscreen(bool fullscreen) { CEF_REQUIRE_UI_THREAD(); if (window_) { // Hide the top controls while in full-screen mode. if (with_controls_) ShowTopControls(!fullscreen); window_->SetFullscreen(fullscreen); } } void ViewsWindow::SetLoadingState(bool isLoading, bool canGoBack, bool canGoForward) { CEF_REQUIRE_UI_THREAD(); if (!window_ || !with_controls_) return; EnableView(ID_BACK_BUTTON, canGoBack); EnableView(ID_FORWARD_BUTTON, canGoForward); EnableView(ID_RELOAD_BUTTON, !isLoading); EnableView(ID_STOP_BUTTON, isLoading); EnableView(ID_URL_TEXTFIELD, true); } void ViewsWindow::SetDraggableRegions( const std::vector& regions) { CEF_REQUIRE_UI_THREAD(); if (!window_ || !browser_view_) return; std::vector window_regions; // Convert the regions from BrowserView to Window coordinates. std::vector::const_iterator it = regions.begin(); for (; it != regions.end(); ++it) { CefDraggableRegion region = *it; CefPoint origin = CefPoint(region.bounds.x, region.bounds.y); browser_view_->ConvertPointToWindow(origin); region.bounds.x = origin.x; region.bounds.y = origin.y; window_regions.push_back(region); } window_->SetDraggableRegions(window_regions); } void ViewsWindow::TakeFocus(bool next) { CEF_REQUIRE_UI_THREAD(); if (!window_ || !with_controls_) return; // Give focus to the URL textfield. window_->GetViewForID(ID_URL_TEXTFIELD)->RequestFocus(); } void ViewsWindow::OnBeforeContextMenu(CefRefPtr model) { CEF_REQUIRE_UI_THREAD(); views_style::ApplyTo(model); } bool ViewsWindow::OnPopupBrowserViewCreated( CefRefPtr browser_view, CefRefPtr popup_browser_view, bool is_devtools) { CEF_REQUIRE_UI_THREAD(); // The popup browser client is created in CefLifeSpanHandler::OnBeforePopup() // (e.g. via RootWindowViews::InitAsPopup()). The Delegate (RootWindowViews) // knows the association between |client| and itself. Delegate* popup_delegate = delegate_->GetDelegateForPopup( popup_browser_view->GetBrowser()->GetHost()->GetClient()); // Create a new ViewsWindow for the popup BrowserView. CefRefPtr popup_window = new ViewsWindow(popup_delegate, popup_browser_view); // Create a new top-level Window for the popup. It will show itself after // creation. CefWindow::CreateTopLevelWindow(popup_window); // We created the Window. return true; } void ViewsWindow::OnButtonPressed(CefRefPtr button) { CEF_REQUIRE_UI_THREAD(); DCHECK(with_controls_); if (!browser_view_) return; CefRefPtr browser = browser_view_->GetBrowser(); if (!browser) return; switch (button->GetID()) { case ID_BACK_BUTTON: browser->GoBack(); break; case ID_FORWARD_BUTTON: browser->GoForward(); break; case ID_STOP_BUTTON: browser->StopLoad(); break; case ID_RELOAD_BUTTON: browser->Reload(); break; case ID_MENU_BUTTON: break; default: NOTREACHED(); break; } } void ViewsWindow::OnMenuButtonPressed(CefRefPtr menu_button, const CefPoint& screen_point) { CEF_REQUIRE_UI_THREAD(); DCHECK(with_controls_); DCHECK_EQ(ID_MENU_BUTTON, menu_button->GetID()); menu_button->ShowMenu(button_menu_model_, screen_point, CEF_MENU_ANCHOR_TOPRIGHT); } void ViewsWindow::ExecuteCommand(CefRefPtr menu_model, int command_id, cef_event_flags_t event_flags) { CEF_REQUIRE_UI_THREAD(); DCHECK(with_controls_); if (command_id == ID_QUIT) { delegate_->OnExit(); } else if (command_id >= ID_TESTS_FIRST && command_id <= ID_TESTS_LAST) { delegate_->OnTest(command_id); } else { NOTREACHED(); } } bool ViewsWindow::OnKeyEvent(CefRefPtr textfield, const CefKeyEvent& event) { CEF_REQUIRE_UI_THREAD(); DCHECK(with_controls_); DCHECK_EQ(ID_URL_TEXTFIELD, textfield->GetID()); // Trigger when the return key is pressed. if (window_ && browser_view_ && event.type == KEYEVENT_RAWKEYDOWN && event.windows_key_code == VK_RETURN) { CefRefPtr browser = browser_view_->GetBrowser(); if (browser) { CefRefPtr view = window_->GetViewForID(ID_URL_TEXTFIELD); if (view && view->AsTextfield()) { const CefString& url = view->AsTextfield()->GetText(); if (!url.empty()) browser->GetMainFrame()->LoadURL(url); } } // We handled the event. return true; } return false; } void ViewsWindow::OnWindowCreated(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(browser_view_); DCHECK(!window_); DCHECK(window); window_ = window; window_->SetID(ID_WINDOW); with_controls_ = delegate_->WithControls(); delegate_->OnViewsWindowCreated(this); CefRect bounds = delegate_->GetWindowBounds(); if (bounds.IsEmpty()) { // Use the default size. bounds.width = 800; bounds.height = 600; } if (bounds.x == 0 && bounds.y == 0) { // Size the Window and center it. window_->CenterWindow(CefSize(bounds.width, bounds.height)); } else { // Set the Window bounds as specified. window_->SetBounds(bounds); } // Set the background color for regions that are not obscured by other Views. views_style::ApplyTo(window_.get()); if (with_controls_) { // Add the BrowserView and other controls to the Window. AddControls(); // Add keyboard accelerators to the Window. AddAccelerators(); } else { // Add the BrowserView as the only child of the Window. window_->AddChildView(browser_view_); // Choose a reasonable minimum window size. minimum_window_size_ = CefSize(100, 100); } // Show the Window. window_->Show(); // Give keyboard focus to the BrowserView. browser_view_->RequestFocus(); } void ViewsWindow::OnWindowDestroyed(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(window_); delegate_->OnViewsWindowDestroyed(this); browser_view_ = NULL; button_menu_model_ = NULL; if (top_menu_bar_) { top_menu_bar_->Reset(); top_menu_bar_ = NULL; } window_ = NULL; } bool ViewsWindow::CanClose(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); // Allow the window to close if the browser says it's OK. CefRefPtr browser = browser_view_->GetBrowser(); if (browser) return browser->GetHost()->TryCloseBrowser(); return true; } bool ViewsWindow::IsFrameless(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); return frameless_; } bool ViewsWindow::OnAccelerator(CefRefPtr window, int command_id) { CEF_REQUIRE_UI_THREAD(); if (command_id == ID_QUIT) { delegate_->OnExit(); return true; } return false; } bool ViewsWindow::OnKeyEvent(CefRefPtr window, const CefKeyEvent& event) { CEF_REQUIRE_UI_THREAD(); if (!window_ || !with_controls_) return false; if (event.type == KEYEVENT_RAWKEYDOWN && event.windows_key_code == VK_MENU) { // ALT key is pressed. int last_focused_view = last_focused_view_; bool menu_had_focus = menu_has_focus_; // Toggle menu button focusable. SetMenuFocusable(!menu_has_focus_); if (menu_had_focus && last_focused_view != 0) { // Restore focus to the view that was previously focused. window_->GetViewForID(last_focused_view)->RequestFocus(); } return true; } if (menu_has_focus_ && top_menu_bar_) return top_menu_bar_->OnKeyEvent(event); return false; } CefSize ViewsWindow::GetMinimumSize(CefRefPtr view) { CEF_REQUIRE_UI_THREAD(); if (view->GetID() == ID_WINDOW) return minimum_window_size_; return CefSize(); } void ViewsWindow::OnFocus(CefRefPtr view) { CEF_REQUIRE_UI_THREAD(); const int view_id = view->GetID(); // Keep track of the non-menu view that was last focused. if (last_focused_view_ != view_id && (!top_menu_bar_ || !top_menu_bar_->HasMenuId(view_id))) { last_focused_view_ = view_id; } // When focus leaves the menu buttons make them unfocusable. if (menu_has_focus_) { if (top_menu_bar_) { if (!top_menu_bar_->HasMenuId(view_id)) SetMenuFocusable(false); } else if (view_id != ID_MENU_BUTTON) { SetMenuFocusable(false); } } } void ViewsWindow::MenuBarExecuteCommand(CefRefPtr menu_model, int command_id, cef_event_flags_t event_flags) { ExecuteCommand(menu_model, command_id, event_flags); } ViewsWindow::ViewsWindow(Delegate* delegate, CefRefPtr browser_view) : delegate_(delegate), with_controls_(false), menu_has_focus_(false), last_focused_view_(false) { DCHECK(delegate_); if (browser_view) SetBrowserView(browser_view); CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); frameless_ = command_line->HasSwitch(switches::kHideFrame); if (!command_line->HasSwitch(switches::kHideTopMenu)) { top_menu_bar_ = new ViewsMenuBar(this, ID_TOP_MENU_FIRST); } } void ViewsWindow::SetBrowserView(CefRefPtr browser_view) { DCHECK(!browser_view_); DCHECK(browser_view); DCHECK(browser_view->IsValid()); DCHECK(!browser_view->IsAttached()); browser_view_ = browser_view; browser_view_->SetID(ID_BROWSER_VIEW); } void ViewsWindow::CreateMenuModel() { // Create the menu button model. button_menu_model_ = CefMenuModel::CreateMenuModel(this); CefRefPtr test_menu = button_menu_model_->AddSubMenu(0, "&Tests"); views_style::ApplyTo(button_menu_model_); AddTestMenuItems(test_menu); AddFileMenuItems(button_menu_model_); if (top_menu_bar_) { // Add the menus to the top menu bar. AddFileMenuItems(top_menu_bar_->CreateMenuModel("&File", NULL)); AddTestMenuItems(top_menu_bar_->CreateMenuModel("&Tests", NULL)); } } CefRefPtr ViewsWindow::CreateBrowseButton( const std::string& label, int id) { // The default framed button image resources (IDR_BUTTON_*) look pretty bad // with non-default background colors, so we'll use frameless buttons with // ink drop when a background color is specified. const bool with_frame = !views_style::IsSet(); CefRefPtr button = CefLabelButton::CreateLabelButton(this, label, with_frame); button->SetID(id); button->SetEnabled(false); // Disabled by default. button->SetFocusable(false); // Don't give focus to the button. if (!with_frame) { views_style::ApplyTo(button); button->SetInkDropEnabled(true); button->SetHorizontalAlignment(CEF_HORIZONTAL_ALIGNMENT_CENTER); } return button; } void ViewsWindow::AddControls() { // Create the MenuModel that will be displayed via the menu button. CreateMenuModel(); CefRefPtr top_menu_panel; if (top_menu_bar_) top_menu_panel = top_menu_bar_->GetMenuPanel(); // Create the browse buttons. LabelButtons browse_buttons; browse_buttons.push_back(CreateBrowseButton("Back", ID_BACK_BUTTON)); browse_buttons.push_back(CreateBrowseButton("Forward", ID_FORWARD_BUTTON)); browse_buttons.push_back(CreateBrowseButton("Reload", ID_RELOAD_BUTTON)); browse_buttons.push_back(CreateBrowseButton("Stop", ID_STOP_BUTTON)); // Create the URL textfield. CefRefPtr url_textfield = CefTextfield::CreateTextfield(this); url_textfield->SetID(ID_URL_TEXTFIELD); url_textfield->SetEnabled(false); // Disabled by default. views_style::ApplyTo(url_textfield); // Create the menu button. CefRefPtr menu_button = CefMenuButton::CreateMenuButton(this, CefString(), false, false); menu_button->SetID(ID_MENU_BUTTON); menu_button->SetImage(CEF_BUTTON_STATE_NORMAL, LoadImageIcon("menu_icon")); views_style::ApplyTo(menu_button.get()); menu_button->SetInkDropEnabled(true); // Override the default minimum size. menu_button->SetMinimumSize(CefSize(0, 0)); // Create the top panel. CefRefPtr top_panel = CefPanel::CreatePanel(NULL); // Use a horizontal box layout for |top_panel|. CefBoxLayoutSettings top_panel_layout_settings; top_panel_layout_settings.horizontal = true; CefRefPtr top_panel_layout = top_panel->SetToBoxLayout(top_panel_layout_settings); // Add the buttons and URL textfield to |top_panel|. for (size_t i = 0U; i < browse_buttons.size(); ++i) top_panel->AddChildView(browse_buttons[i]); top_panel->AddChildView(url_textfield); top_panel->AddChildView(menu_button); views_style::ApplyTo(top_panel); // Allow |url_textfield| to grow and fill any remaining space. top_panel_layout->SetFlexForView(url_textfield, 1); // Use a vertical box layout for |window|. CefBoxLayoutSettings window_layout_settings; window_layout_settings.horizontal = false; window_layout_settings.between_child_spacing = 2; CefRefPtr window_layout = window_->SetToBoxLayout(window_layout_settings); // Add the top panel and browser view to |window|. if (top_menu_panel) window_->AddChildView(top_menu_panel); window_->AddChildView(top_panel); window_->AddChildView(browser_view_); // Allow |browser_view_| to grow and fill any remaining space. window_layout->SetFlexForView(browser_view_, 1); // Lay out |window| so we can get the default button sizes. window_->Layout(); // Make all browse buttons the same size. MakeButtonsSameSize(browse_buttons); // Lay out |window| again with the new button sizes. window_->Layout(); // Minimum window width is the size of all buttons plus some extra. const int min_width = browse_buttons[0]->GetBounds().width * 4 + menu_button->GetBounds().width + 100; // Minimum window height is the hight of the top toolbar plus some extra. int min_height = top_panel->GetBounds().height + 100; if (top_menu_panel) min_height += top_menu_panel->GetBounds().height; minimum_window_size_ = CefSize(min_width, min_height); } void ViewsWindow::AddAccelerators() { // Trigger accelerators without first forwarding to web content. browser_view_->SetPreferAccelerators(true); // Specify the accelerators to handle. OnAccelerator will be called when the // accelerator is triggered. window_->SetAccelerator(ID_QUIT, 'X', false, false, true); } void ViewsWindow::SetMenuFocusable(bool focusable) { if (!window_ || !with_controls_) return; if (top_menu_bar_) { top_menu_bar_->SetMenuFocusable(focusable); } else { window_->GetViewForID(ID_MENU_BUTTON)->SetFocusable(focusable); if (focusable) { // Give focus to menu button. window_->GetViewForID(ID_MENU_BUTTON)->RequestFocus(); } } menu_has_focus_ = focusable; } void ViewsWindow::EnableView(int id, bool enable) { if (!window_) return; CefRefPtr view = window_->GetViewForID(id); if (view) view->SetEnabled(enable); } void ViewsWindow::ShowTopControls(bool show) { if (!window_ || !with_controls_) return; // Change the visibility of the panel that contains the buttons. CefRefPtr parent_view = window_->GetViewForID(ID_BACK_BUTTON)->GetParentView(); if (parent_view->IsVisible() != show) { parent_view->SetVisible(show); parent_view->InvalidateLayout(); } } } // namespace client