// 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_build.h" #include "include/base/cef_callback.h" #include "include/cef_app.h" #include "include/cef_i18n_util.h" #include "include/views/cef_box_layout.h" #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/main_context.h" #include "tests/cefclient/browser/resource.h" #include "tests/cefclient/browser/views_style.h" #include "tests/shared/browser/extension_util.h" #include "tests/shared/common/client_switches.h" #if !defined(OS_WIN) #define VK_ESCAPE 0x1B #define VK_RETURN 0x0D #define VK_MENU 0x12 // ALT key. #endif namespace client { namespace { const char kDefaultExtensionIcon[] = "window_icon"; // Default window size. constexpr int kDefaultWidth = 800; constexpr int kDefaultHeight = 600; #if defined(OS_MAC) constexpr int kTitleBarHeight = 35; constexpr int kWindowButtonsWidth = 80; #endif // 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, // Reserved range of extension button IDs. ID_EXTENSION_BUTTON_FIRST, ID_EXTENSION_BUTTON_LAST = ID_EXTENSION_BUTTON_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_WINDOW_DIALOG, "Dialog Window"); test_menu->AddItem(ID_TESTS_REQUEST, "Request"); 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_MUTE_AUDIO, "Mute Audio"); test_menu->AddItem(ID_TESTS_UNMUTE_AUDIO, "Unmute Audio"); 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); } CefBrowserViewDelegate::ChromeToolbarType CalculateChromeToolbarType( const std::string& toolbar_type, bool hide_toolbar, bool with_overlay_controls) { if (!MainContext::Get()->UseChromeRuntime() || toolbar_type == "none" || hide_toolbar) { return CEF_CTT_NONE; } if (toolbar_type == "location") { return CEF_CTT_LOCATION; } return with_overlay_controls ? CEF_CTT_LOCATION : CEF_CTT_NORMAL; } } // namespace // static CefRefPtr ViewsWindow::Create( WindowType type, Delegate* delegate, CefRefPtr client, const CefString& url, const CefBrowserSettings& settings, CefRefPtr request_context, CefRefPtr command_line) { CEF_REQUIRE_UI_THREAD(); DCHECK(delegate); // Create a new ViewsWindow. CefRefPtr views_window = new ViewsWindow(type, delegate, nullptr, command_line); // Create a new BrowserView. CefRefPtr browser_view = CefBrowserView::CreateBrowserView( client, url, settings, nullptr, 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_) { if (type_ == WindowType::DIALOG) { if (use_window_modal_dialog_) { // Show as a window modal dialog (IsWindowModalDialog() will return // true). window_->Show(); } else { CefRefPtr browser_view; if (auto parent_window = delegate_->GetParentWindow()) { if (auto view = parent_window->GetViewForID(ID_BROWSER_VIEW)) { browser_view = view->AsBrowserView(); } } CHECK(browser_view); // Show as a browser modal dialog (relative to |browser_view|). window_->ShowAsBrowserModalDialog(browser_view); } } else { window_->Show(); } } if (browser_view_ && !window_->IsMinimized()) { // Give keyboard focus to the BrowserView. browser_view_->RequestFocus(); } } 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::SetBrowserSize(const CefSize& size, bool has_position, const CefPoint& position) { CEF_REQUIRE_UI_THREAD(); if (browser_view_) { browser_view_->SetSize(size); } if (window_) { window_->SizeToPreferredSize(); if (has_position) { window_->SetPosition(position); } } } 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_) { return; } // |location_bar_| may instead be a Chrome toolbar. if (location_bar_ && location_bar_->AsTextfield()) { location_bar_->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(); // For Chrome runtime we ignore this notification from // ClientHandler::OnFullscreenModeChange(). Chrome runtime will trigger // the fullscreen change internally and then call // OnWindowFullscreenTransition(). if (MainContext::Get()->UseChromeRuntime()) { return; } // For Alloy runtime we need to explicitly trigger the fullscreen change. if (window_) { // Results in a call to OnWindowFullscreenTransition(). window_->SetFullscreen(fullscreen); } } void ViewsWindow::SetAlwaysOnTop(bool on_top) { CEF_REQUIRE_UI_THREAD(); if (window_) { window_->SetAlwaysOnTop(on_top); } } void ViewsWindow::SetLoadingState(bool isLoading, bool canGoBack, bool canGoForward) { CEF_REQUIRE_UI_THREAD(); if (!window_ || chrome_toolbar_type_ == CEF_CTT_NORMAL) { return; } if (with_controls_) { EnableView(ID_BACK_BUTTON, canGoBack); EnableView(ID_FORWARD_BUTTON, canGoForward); EnableView(ID_RELOAD_BUTTON, !isLoading); EnableView(ID_STOP_BUTTON, isLoading); } if (location_bar_) { EnableView(ID_URL_TEXTFIELD, true); } } void ViewsWindow::SetDraggableRegions( const std::vector& regions) { CEF_REQUIRE_UI_THREAD(); if (!window_ || !browser_view_) { return; } // Convert the regions from BrowserView to Window coordinates. std::vector window_regions = regions; for (auto& region : window_regions) { CefPoint origin = CefPoint(region.bounds.x, region.bounds.y); browser_view_->ConvertPointToWindow(origin); region.bounds.x = origin.x; region.bounds.y = origin.y; } if (overlay_controls_) { // Exclude all regions obscured by overlays. overlay_controls_->UpdateDraggableRegions(window_regions); } window_->SetDraggableRegions(window_regions); } void ViewsWindow::TakeFocus(bool next) { CEF_REQUIRE_UI_THREAD(); if (!window_) { return; } if (chrome_toolbar_type_ == CEF_CTT_NORMAL) { toolbar_->RequestFocus(); } else if (location_bar_) { // Give focus to the location bar. location_bar_->RequestFocus(); } } void ViewsWindow::OnBeforeContextMenu(CefRefPtr model) { CEF_REQUIRE_UI_THREAD(); views_style::ApplyTo(model); } void ViewsWindow::OnExtensionsChanged(const ExtensionSet& extensions) { CEF_REQUIRE_UI_THREAD(); if (extensions.empty()) { if (!extensions_.empty()) { extensions_.clear(); UpdateExtensionControls(); } return; } ImageCache::ImageInfoSet image_set; ExtensionSet::const_iterator it = extensions.begin(); for (; it != extensions.end(); ++it) { CefRefPtr extension = *it; bool internal = false; const std::string& icon_path = extension_util::GetExtensionIconPath(extension, &internal); if (!icon_path.empty()) { // Load the extension icon. image_set.push_back( ImageCache::ImageInfo::Create1x(icon_path, icon_path, internal)); } else { // Get a nullptr image and use the default icon. image_set.push_back(ImageCache::ImageInfo::Empty()); } } delegate_->GetImageCache()->LoadImages( image_set, base::BindOnce(&ViewsWindow::OnExtensionIconsLoaded, this, extensions)); } // static bool ViewsWindow::SupportsWindowRestore(WindowType type) { // Only support window restore with normal windows. return type == WindowType::NORMAL; } bool ViewsWindow::SupportsWindowRestore() const { return SupportsWindowRestore(type_); } bool ViewsWindow::GetWindowRestorePreferences( cef_show_state_t& show_state, std::optional& dip_bounds) { CEF_REQUIRE_UI_THREAD(); DCHECK(SupportsWindowRestore()); if (!window_) { return false; } show_state = CEF_SHOW_STATE_NORMAL; if (window_->IsMinimized()) { show_state = CEF_SHOW_STATE_MINIMIZED; } else if (window_->IsFullscreen()) { // On MacOS, IsMaximized() will also return true for fullscreen, so check // IsFullscreen() first. show_state = CEF_SHOW_STATE_FULLSCREEN; } else if (window_->IsMaximized()) { show_state = CEF_SHOW_STATE_MAXIMIZED; } if (show_state == CEF_SHOW_STATE_NORMAL) { // Use the current visible bounds. dip_bounds = window_->GetBoundsInScreen(); } else { // Use the last known visible bounds. dip_bounds = last_visible_bounds_; } return true; } void ViewsWindow::SetTitlebarHeight(const std::optional& height) { CEF_REQUIRE_UI_THREAD(); if (height.has_value()) { override_titlebar_height_ = height; } else { override_titlebar_height_ = default_titlebar_height_; } NudgeWindow(); } CefRefPtr ViewsWindow::GetDelegateForPopupBrowserView( CefRefPtr browser_view, const CefBrowserSettings& settings, CefRefPtr client, 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(client); // May be nullptr when using the default popup behavior. if (!popup_delegate) { return nullptr; } // Should not be the same RootWindowViews that owns |this|. DCHECK(popup_delegate != delegate_); // Create a new ViewsWindow for the popup BrowserView. return new ViewsWindow( is_devtools ? WindowType::DEVTOOLS : WindowType::NORMAL, popup_delegate, nullptr, command_line_); } bool ViewsWindow::OnPopupBrowserViewCreated( CefRefPtr browser_view, CefRefPtr popup_browser_view, bool is_devtools) { CEF_REQUIRE_UI_THREAD(); // Retrieve the ViewsWindow created in GetDelegateForPopupBrowserView. CefRefPtr popup_window = static_cast(static_cast( popup_browser_view->GetDelegate().get())); // May be nullptr when using the default popup behavior. if (!popup_window) { return false; } // Should not be the same ViewsWindow as |this|. DCHECK(popup_window != this); // Associate the ViewsWindow with the new popup browser. popup_window->SetBrowserView(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; } CefBrowserViewDelegate::ChromeToolbarType ViewsWindow::GetChromeToolbarType( CefRefPtr browser_view) { return chrome_toolbar_type_; } bool ViewsWindow::UseFramelessWindowForPictureInPicture( CefRefPtr browser_view) { return hide_pip_frame_; } 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, CefRefPtr button_pressed_lock) { CEF_REQUIRE_UI_THREAD(); const int id = menu_button->GetID(); if (id >= ID_EXTENSION_BUTTON_FIRST && id <= ID_EXTENSION_BUTTON_LAST) { const size_t extension_idx = id - ID_EXTENSION_BUTTON_FIRST; if (extension_idx >= extensions_.size()) { LOG(ERROR) << "Invalid extension index " << extension_idx; return; } // Keep the button pressed until the extension window is closed. extension_button_pressed_lock_ = button_pressed_lock; // Create a window for the extension. delegate_->CreateExtensionWindow( extensions_[extension_idx].extension_, menu_button->GetBoundsInScreen(), window_, base::BindOnce(&ViewsWindow::OnExtensionWindowClosed, this)); return; } DCHECK(with_controls_ || with_overlay_controls_); DCHECK_EQ(ID_MENU_BUTTON, id); const auto button_bounds = menu_button->GetBoundsInScreen(); auto point = screen_point; if (with_overlay_controls_) { // Align the menu correctly under the button. if (CefIsRTL()) { point.x += button_bounds.width - 4; } else { point.x -= button_bounds.width - 4; } } if (use_bottom_controls_) { const auto display_bounds = menu_button->GetWindow()->GetDisplay()->GetWorkArea(); const int available_height = display_bounds.y + display_bounds.height - button_bounds.y - button_bounds.height; // Approximation of the menu height. const int menu_height = static_cast(button_menu_model_->GetCount()) * button_bounds.height; if (menu_height > available_height) { // The menu will go upwards, so place it above the button. point.y -= button_bounds.height - 8; } } menu_button->ShowMenu(button_menu_model_, point, with_overlay_controls_ ? CEF_MENU_ANCHOR_TOPLEFT : 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_ || with_overlay_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_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) { const CefString& url = textfield->GetText(); if (!url.empty()) { browser->GetMainFrame()->LoadURL(url); } } // We handled the event. return true; } return false; } void ViewsWindow::OnWindowFullscreenTransition(CefRefPtr window, bool is_completed) { #if defined(OS_MAC) // On MacOS we get two asynchronous callbacks, and we want to change the UI on // |is_completed=false| (e.g. when the fullscreen transition begins). const bool should_change = !is_completed; #else // On other platforms we only get a single synchronous callback with // |is_completed=true|. DCHECK(is_completed); const bool should_change = true; #endif // Hide the top controls while in fullscreen mode. if (should_change && with_controls_) { ShowTopControls(!window->IsFullscreen()); } // With Alloy runtime we need to explicitly exit browser fullscreen when // exiting window fullscreen. Chrome runtime handles this internally. if (!MainContext::Get()->UseChromeRuntime() && should_change && !window->IsFullscreen()) { CefRefPtr browser = browser_view_->GetBrowser(); if (browser && browser->GetHost()->IsFullscreen()) { // Will not cause a resize because the fullscreen transition has already // begun. browser->GetHost()->ExitFullscreen(/*will_cause_resize=*/false); } } } void ViewsWindow::OnWindowCreated(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(browser_view_); DCHECK(!window_); DCHECK(window); window_ = window; window_->SetID(ID_WINDOW); delegate_->OnViewsWindowCreated(this); if (type_ == WindowType::NORMAL || type_ == WindowType::DEVTOOLS) { const CefRect bounds = delegate_->GetInitialBounds(); if (bounds.IsEmpty()) { // Size the Window and center it at the default size. window_->CenterWindow(CefSize(kDefaultWidth, kDefaultHeight)); } else if (SupportsWindowRestore()) { // Remember the bounds from the previous application run in case the user // does not move or resize the window during this application run. last_visible_bounds_ = bounds; } } // Set the background color for regions that are not obscured by other Views. views_style::ApplyTo(window_.get()); if (with_controls_ || with_overlay_controls_) { // Create the MenuModel that will be displayed via the menu button. CreateMenuModel(); } if (with_controls_) { // Add the BrowserView and other controls to the Window. AddBrowserView(); // Add keyboard accelerators to the Window. AddAccelerators(); // Hide the top controls while in full-screen mode. if (delegate_->GetInitialShowState() == CEF_SHOW_STATE_FULLSCREEN) { ShowTopControls(false); } } else { // Add the BrowserView as the only child of the Window. window_->AddChildView(browser_view_); if (type_ != WindowType::EXTENSION) { // Choose a reasonable minimum window size. minimum_window_size_ = CefSize(100, 100); } } if (!delegate_->InitiallyHidden()) { // Show the Window. Show(); } } void ViewsWindow::OnWindowClosing(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(window_); delegate_->OnViewsWindowClosing(this); } void ViewsWindow::OnWindowDestroyed(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(window_); delegate_->OnViewsWindowDestroyed(this); browser_view_ = nullptr; button_menu_model_ = nullptr; if (menu_bar_) { menu_bar_->Reset(); menu_bar_ = nullptr; } extensions_panel_ = nullptr; menu_button_ = nullptr; window_ = nullptr; } void ViewsWindow::OnWindowActivationChanged(CefRefPtr window, bool active) { if (!active) { return; } delegate_->OnViewsWindowActivated(this); } void ViewsWindow::OnWindowBoundsChanged(CefRefPtr window, const CefRect& new_bounds) { if (SupportsWindowRestore() && !window->IsMinimized() && !window->IsMaximized() && !window->IsFullscreen()) { // Track the last visible bounds for window restore purposes. last_visible_bounds_ = new_bounds; } #if defined(OS_MAC) if (frameless_ && with_standard_buttons_ && toolbar_) { auto insets = toolbar_->GetInsets(); insets.left = window->IsFullscreen() ? 0 : kWindowButtonsWidth; toolbar_->SetInsets(insets); } #endif } 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; } CefRefPtr ViewsWindow::GetParentWindow(CefRefPtr window, bool* is_menu, bool* can_activate_menu) { CEF_REQUIRE_UI_THREAD(); CefRefPtr parent_window = delegate_->GetParentWindow(); if (parent_window) { // Extension windows behave as a menu and allow activation. if (type_ == WindowType::EXTENSION) { *is_menu = true; *can_activate_menu = true; } } return parent_window; } bool ViewsWindow::IsWindowModalDialog(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(delegate_->GetParentWindow()); return use_window_modal_dialog_; } CefRect ViewsWindow::GetInitialBounds(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); const CefRect bounds = delegate_->GetInitialBounds(); if (frameless_ && bounds.IsEmpty()) { // Need to provide a size for frameless windows that will be centered. return CefRect(0, 0, kDefaultWidth, kDefaultHeight); } return bounds; } cef_show_state_t ViewsWindow::GetInitialShowState(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); return delegate_->GetInitialShowState(); } bool ViewsWindow::IsFrameless(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); return frameless_; } bool ViewsWindow::WithStandardWindowButtons(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); return with_standard_buttons_; } bool ViewsWindow::GetTitlebarHeight(CefRefPtr window, float* titlebar_height) { CEF_REQUIRE_UI_THREAD(); #if defined(OS_MAC) if (override_titlebar_height_.has_value()) { *titlebar_height = override_titlebar_height_.value(); return true; } #endif return false; } bool ViewsWindow::CanResize(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); // Only allow resize of normal and DevTools windows. return type_ == WindowType::NORMAL || type_ == WindowType::DEVTOOLS; } bool ViewsWindow::CanMaximize(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); return CanResize(window); } bool ViewsWindow::CanMinimize(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); return CanResize(window); } 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_) { return false; } if (type_ == WindowType::EXTENSION && event.type == KEYEVENT_RAWKEYDOWN && event.windows_key_code == VK_ESCAPE) { // Close the extension window on escape. Close(false); return true; } if (!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_ && menu_bar_) { return menu_bar_->OnKeyEvent(event); } return false; } CefSize ViewsWindow::GetPreferredSize(CefRefPtr view) { CEF_REQUIRE_UI_THREAD(); if (view->GetID() == ID_WINDOW && type_ == WindowType::DIALOG) { // Preferred size for a browser modal dialog. The dialog will be shrunk to // fit inside the parent browser view if necessary. return CefSize(kDefaultWidth, kDefaultHeight); } return CefSize(); } 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 && (!menu_bar_ || !menu_bar_->HasMenuId(view_id))) { last_focused_view_ = view_id; } // When focus leaves the menu buttons make them unfocusable. if (menu_has_focus_) { if (menu_bar_) { if (!menu_bar_->HasMenuId(view_id)) { SetMenuFocusable(false); } } else if (view_id != ID_MENU_BUTTON) { SetMenuFocusable(false); } } } void ViewsWindow::OnBlur(CefRefPtr view) { CEF_REQUIRE_UI_THREAD(); const int view_id = view->GetID(); if (view_id == ID_BROWSER_VIEW && type_ == WindowType::EXTENSION) { // Close windows hosting extensions when the browser loses focus. Close(false); } } void ViewsWindow::OnWindowChanged(CefRefPtr view, bool added) { const int view_id = view->GetID(); if (view_id != ID_BROWSER_VIEW) { return; } if (added) { if (with_controls_) { AddControls(); } if (with_overlay_controls_) { // Add window buttons if we don't have standard ones const bool with_window_buttons = !with_standard_buttons_; overlay_controls_ = new ViewsOverlayControls(with_window_buttons, use_bottom_controls_); overlay_controls_->Initialize(window_, CreateMenuButton(), CreateLocationBar(), chrome_toolbar_type_ != CEF_CTT_NONE); } } else { // Remove any controls that may include the Chrome toolbar before removing // the BrowserView. if (overlay_controls_) { overlay_controls_->Destroy(); overlay_controls_ = nullptr; location_bar_ = nullptr; } else if (use_bottom_controls_) { if (toolbar_) { window_->RemoveChildView(toolbar_); toolbar_ = nullptr; location_bar_ = nullptr; } } } } void ViewsWindow::OnLayoutChanged(CefRefPtr view, const CefRect& new_bounds) { const int view_id = view->GetID(); if (view_id != ID_BROWSER_VIEW) { return; } if (overlay_controls_) { overlay_controls_->UpdateControls(); } } void ViewsWindow::MenuBarExecuteCommand(CefRefPtr menu_model, int command_id, cef_event_flags_t event_flags) { ExecuteCommand(menu_model, command_id, event_flags); } ViewsWindow::ViewsWindow(WindowType type, Delegate* delegate, CefRefPtr browser_view, CefRefPtr command_line) : type_(type), delegate_(delegate), command_line_(command_line), menu_has_focus_(false), last_focused_view_(false) { DCHECK(delegate_); if (browser_view) { SetBrowserView(browser_view); } const bool is_normal_type = type_ == WindowType::NORMAL; with_controls_ = is_normal_type && delegate_->WithControls(); const bool hide_frame = command_line->HasSwitch(switches::kHideFrame); const bool hide_overlays = !is_normal_type || command_line->HasSwitch(switches::kHideOverlays); const bool hide_toolbar = hide_overlays && !with_controls_; const bool show_window_buttons = command_line->HasSwitch(switches::kShowWindowButtons); // Without a window frame. frameless_ = hide_frame || type_ == WindowType::EXTENSION; // With an overlay that mimics window controls. with_overlay_controls_ = hide_frame && !hide_overlays && !with_controls_; // If window has frame or flag passed explicitly with_standard_buttons_ = !frameless_ || show_window_buttons; #if defined(OS_MAC) if (frameless_ && with_standard_buttons_) { default_titlebar_height_ = kTitleBarHeight; override_titlebar_height_ = kTitleBarHeight; } #endif const std::string& toolbar_type = command_line->GetSwitchValue(switches::kShowChromeToolbar); chrome_toolbar_type_ = CalculateChromeToolbarType(toolbar_type, hide_toolbar, with_overlay_controls_); use_bottom_controls_ = command_line->HasSwitch(switches::kUseBottomControls); #if !defined(OS_MAC) // On Mac we don't show a top menu on the window. The options are available in // the app menu instead. if (!command_line->HasSwitch(switches::kHideTopMenu)) { menu_bar_ = new ViewsMenuBar(this, ID_TOP_MENU_FIRST, use_bottom_controls_); } #endif use_window_modal_dialog_ = command_line->HasSwitch(switches::kUseWindowModalDialog); hide_pip_frame_ = command_line->HasSwitch(switches::kHidePipFrame); } 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 (menu_bar_) { // Add the menus to the top menu bar. AddFileMenuItems(menu_bar_->CreateMenuModel("&File", nullptr)); AddTestMenuItems(menu_bar_->CreateMenuModel("&Tests", nullptr)); } } CefRefPtr ViewsWindow::CreateBrowseButton( const std::string& label, int id) { CefRefPtr button = CefLabelButton::CreateLabelButton(this, label); button->SetID(id); views_style::ApplyTo(button.get()); button->SetInkDropEnabled(true); button->SetEnabled(false); // Disabled by default. button->SetFocusable(false); // Don't give focus to the button. return button; } CefRefPtr ViewsWindow::CreateMenuButton() { // Create the menu button. DCHECK(!menu_button_); menu_button_ = CefMenuButton::CreateMenuButton(this, CefString()); menu_button_->SetID(ID_MENU_BUTTON); menu_button_->SetImage( CEF_BUTTON_STATE_NORMAL, delegate_->GetImageCache()->GetCachedImage("menu_icon")); views_style::ApplyTo(menu_button_.get()); menu_button_->SetInkDropEnabled(true); // Override the default minimum size. menu_button_->SetMinimumSize(CefSize(0, 0)); return menu_button_; } CefRefPtr ViewsWindow::CreateLocationBar() { DCHECK(!location_bar_); if (chrome_toolbar_type_ == CEF_CTT_LOCATION) { // Chrome will provide a minimal location bar. location_bar_ = browser_view_->GetChromeToolbar(); DCHECK(location_bar_); views_style::ApplyBackgroundTo(location_bar_); } if (!location_bar_) { // 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); location_bar_ = url_textfield; } return location_bar_; } void ViewsWindow::AddBrowserView() { // 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); window_->AddChildView(browser_view_); // Allow |browser_view_| to grow and fill any remaining space. window_layout->SetFlexForView(browser_view_, 1); // Remaining setup will be performed in OnWindowChanged after the BrowserView // is added to the CefWindow. This is necessary because Chrome toolbars are // only available after the BrowserView is added. } void ViewsWindow::AddControls() { // Build the remainder of the UI now that the BrowserView has been added to // the CefWindow. This is a requirement to use Chrome toolbars. CefRefPtr menu_panel; if (menu_bar_) { menu_panel = menu_bar_->GetMenuPanel(); } LabelButtons browse_buttons; if (chrome_toolbar_type_ == CEF_CTT_NORMAL) { // Chrome will provide a normal toolbar with location, menu, etc. toolbar_ = browser_view_->GetChromeToolbar(); DCHECK(toolbar_); } if (!toolbar_) { // Create the 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)); CreateLocationBar(); CreateMenuButton(); // Create the toolbar panel. CefRefPtr panel = CefPanel::CreatePanel(nullptr); // Use a horizontal box layout for |panel|. CefBoxLayoutSettings panel_layout_settings; panel_layout_settings.horizontal = true; CefRefPtr panel_layout = panel->SetToBoxLayout(panel_layout_settings); // Add the buttons and URL textfield to |panel|. for (auto& browse_button : browse_buttons) { panel->AddChildView(browse_button); } panel->AddChildView(location_bar_); UpdateExtensionControls(); DCHECK(extensions_panel_); panel->AddChildView(extensions_panel_); panel->AddChildView(menu_button_); views_style::ApplyTo(panel); // Allow |location| to grow and fill any remaining space. panel_layout->SetFlexForView(location_bar_, 1); toolbar_ = panel; } #if defined(OS_MAC) if (frameless_ && with_standard_buttons_) { auto insets = toolbar_->GetInsets(); insets.left = kWindowButtonsWidth; toolbar_->SetInsets(insets); } #endif if (use_bottom_controls_) { // Add the panel at the bottom of |window|. window_->AddChildView(toolbar_); if (menu_panel) { window_->AddChildView(menu_panel); } } else { // Add the panel at the top of |window|. int index = 0; if (menu_panel) { window_->AddChildViewAt(menu_panel, index++); } window_->AddChildViewAt(toolbar_, index); } // Lay out |window| so we can get the default button sizes. window_->Layout(); int min_width = 200; if (!browse_buttons.empty()) { // Make all browse buttons the same size. MakeButtonsSameSize(browse_buttons); // Lay out |window| again with the new button sizes. window_->Layout(); const int buttons_number = static_cast(browse_buttons.size()); // Minimum window width is the size of all buttons plus some extra. min_width = browse_buttons[0]->GetBounds().width * buttons_number + menu_button_->GetBounds().width + 100; } // Minimum window height is the hight of the toolbar plus some extra. int min_height = toolbar_->GetBounds().height + 100; if (menu_panel) { min_height += menu_panel->GetBounds().height; } minimum_window_size_ = CefSize(min_width, min_height); } void ViewsWindow::AddAccelerators() { // Specify the accelerators to handle. OnAccelerator will be called when the // accelerator is triggered. window_->SetAccelerator(ID_QUIT, 'X', /*shift_pressed=*/false, /*ctrl_pressed=*/false, /*alt_pressed=*/true, /*high_priority=*/true); } void ViewsWindow::SetMenuFocusable(bool focusable) { if (!window_ || !with_controls_) { return; } if (menu_bar_) { 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; } // Special handling for |location_bar_| which may be an overlay (e.g. not a // child of this view). CefRefPtr view = id == ID_URL_TEXTFIELD ? location_bar_ : window_->GetViewForID(id); if (view) { view->SetEnabled(enable); } } void ViewsWindow::ShowTopControls(bool show) { if (!window_ || !with_controls_) { return; } // Change the visibility of the toolbar. if (toolbar_->IsVisible() != show) { toolbar_->SetVisible(show); toolbar_->InvalidateLayout(); } } void ViewsWindow::UpdateExtensionControls() { CEF_REQUIRE_UI_THREAD(); if (!window_ || !with_controls_) { return; } if (!extensions_panel_) { extensions_panel_ = CefPanel::CreatePanel(nullptr); // Use a horizontal box layout for |panel|. CefBoxLayoutSettings panel_layout_settings; panel_layout_settings.horizontal = true; CefRefPtr panel_layout = extensions_panel_->SetToBoxLayout(panel_layout_settings); } else { extensions_panel_->RemoveAllChildViews(); } if (extensions_.size() > ID_EXTENSION_BUTTON_LAST - ID_EXTENSION_BUTTON_FIRST) { LOG(WARNING) << "Too many extensions loaded. Some will be ignored."; } ExtensionInfoSet::const_iterator it = extensions_.begin(); for (int id = ID_EXTENSION_BUTTON_FIRST; it != extensions_.end() && id <= ID_EXTENSION_BUTTON_LAST; ++id, ++it) { CefRefPtr button = CefMenuButton::CreateMenuButton(this, CefString()); button->SetID(id); button->SetImage(CEF_BUTTON_STATE_NORMAL, (*it).image_); views_style::ApplyTo(button.get()); button->SetInkDropEnabled(true); // Override the default minimum size. button->SetMinimumSize(CefSize(0, 0)); extensions_panel_->AddChildView(button); } CefRefPtr parent_view = extensions_panel_->GetParentView(); if (parent_view) { parent_view->InvalidateLayout(); } } void ViewsWindow::OnExtensionIconsLoaded(const ExtensionSet& extensions, const ImageCache::ImageSet& images) { if (!CefCurrentlyOn(TID_UI)) { // Execute this method on the UI thread. CefPostTask(TID_UI, base::BindOnce(&ViewsWindow::OnExtensionIconsLoaded, this, extensions, images)); return; } DCHECK_EQ(extensions.size(), images.size()); extensions_.clear(); ExtensionSet::const_iterator it1 = extensions.begin(); ImageCache::ImageSet::const_iterator it2 = images.begin(); for (; it1 != extensions.end() && it2 != images.end(); ++it1, ++it2) { CefRefPtr icon = *it2; if (!icon) { icon = delegate_->GetImageCache()->GetCachedImage(kDefaultExtensionIcon); } extensions_.push_back(ExtensionInfo(*it1, icon)); } UpdateExtensionControls(); } void ViewsWindow::OnExtensionWindowClosed() { if (!CefCurrentlyOn(TID_UI)) { // Execute this method on the UI thread. CefPostTask(TID_UI, base::BindOnce(&ViewsWindow::OnExtensionWindowClosed, this)); return; } // Restore the button state. extension_button_pressed_lock_ = nullptr; } #if !defined(OS_MAC) void ViewsWindow::NudgeWindow() { NOTIMPLEMENTED(); } #endif } // namespace client