chrome: Add ability to hide toolbar and app menu contents (see issue #3280)

This change adds new CefCommandHandler callbacks for optionally hiding
specific Chrome toolbar icons, buttons and app menu items.

To test: Run `cefclient --enable-chrome-runtime --filter-chrome-commands`.
Most icons, buttons and app/context menu items will be hidden.
This commit is contained in:
Marshall Greenblatt
2023-03-06 18:21:57 -05:00
parent 06af9c85da
commit 14dd0c0d06
15 changed files with 866 additions and 77 deletions

View File

@@ -301,6 +301,133 @@ void OnTestSMRProcessMessageReceived(
frame->SendProcessMessage(PID_RENDERER, builder->Build());
}
bool IsAllowedPageActionIcon(cef_chrome_page_action_icon_type_t icon_type) {
// Only the specified icons will be allowed.
switch (icon_type) {
case CEF_CPAIT_FIND:
case CEF_CPAIT_ZOOM:
return true;
default:
break;
}
return false;
}
bool IsAllowedToolbarButton(cef_chrome_toolbar_button_type_t button_type) {
// All configurable buttons will be disabled.
return false;
}
bool IsAllowedAppMenuCommandId(int command_id) {
// Only the commands in this array will be allowed.
static const int kAllowedCommandIds[] = {
IDC_NEW_WINDOW,
IDC_NEW_INCOGNITO_WINDOW,
// Zoom buttons.
IDC_ZOOM_MENU,
IDC_ZOOM_PLUS,
IDC_ZOOM_NORMAL,
IDC_ZOOM_MINUS,
IDC_FULLSCREEN,
IDC_PRINT,
IDC_FIND,
// "More tools" sub-menu and contents.
IDC_MORE_TOOLS_MENU,
IDC_CLEAR_BROWSING_DATA,
IDC_MANAGE_EXTENSIONS,
IDC_PERFORMANCE,
IDC_TASK_MANAGER,
IDC_DEV_TOOLS,
// Edit buttons.
IDC_EDIT_MENU,
IDC_CUT,
IDC_COPY,
IDC_PASTE,
IDC_OPTIONS,
IDC_EXIT,
};
for (size_t i = 0; i < std::size(kAllowedCommandIds); ++i) {
if (command_id == kAllowedCommandIds[i]) {
return true;
}
}
return false;
}
bool IsAllowedContextMenuCommandId(int command_id) {
// Allow commands added by web content.
if (command_id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
command_id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
return true;
}
// Allow commands added by extensions.
if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
return true;
}
// Only the commands in this array will be allowed.
static const int kAllowedCommandIds[] = {
// Page navigation.
IDC_BACK,
IDC_FORWARD,
IDC_RELOAD,
IDC_RELOAD_BYPASSING_CACHE,
IDC_RELOAD_CLEARING_CACHE,
IDC_STOP,
// Printing.
IDC_PRINT,
// Edit controls.
IDC_CONTENT_CONTEXT_CUT,
IDC_CONTENT_CONTEXT_COPY,
IDC_CONTENT_CONTEXT_PASTE,
IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE,
IDC_CONTENT_CONTEXT_DELETE,
IDC_CONTENT_CONTEXT_SELECTALL,
IDC_CONTENT_CONTEXT_UNDO,
IDC_CONTENT_CONTEXT_REDO,
};
for (size_t i = 0; i < std::size(kAllowedCommandIds); ++i) {
if (command_id == kAllowedCommandIds[i]) {
return true;
}
}
return false;
}
void FilterContextMenuModel(CefRefPtr<CefMenuModel> model) {
// Evaluate from the bottom to the top because we'll be removing menu items.
for (size_t x = model->GetCount(); x > 0; --x) {
const auto i = x - 1;
const auto type = model->GetTypeAt(i);
if (type == MENUITEMTYPE_SUBMENU) {
// Filter sub-menu and remove if empty.
auto sub_model = model->GetSubMenuAt(i);
FilterContextMenuModel(sub_model);
if (sub_model->GetCount() == 0) {
model->RemoveAt(i);
}
} else if (type == MENUITEMTYPE_SEPARATOR) {
// A separator shouldn't be the first or last element in the menu, and
// there shouldn't be multiple in a row.
if (i == 0 || i == model->GetCount() - 1 ||
model->GetTypeAt(i + 1) == MENUITEMTYPE_SEPARATOR) {
model->RemoveAt(i);
}
} else if (!IsAllowedContextMenuCommandId(model->GetCommandIdAt(i))) {
model->RemoveAt(i);
}
}
}
} // namespace
class ClientDownloadImageCallback : public CefDownloadImageCallback {
@@ -343,6 +470,8 @@ ClientHandler::ClientHandler(Delegate* delegate,
mouse_cursor_change_disabled_ =
command_line->HasSwitch(switches::kMouseCursorChangeDisabled);
offline_ = command_line->HasSwitch(switches::kOffline);
filter_chrome_commands_ =
command_line->HasSwitch(switches::kFilterChromeCommands);
#if defined(OS_LINUX)
// Optionally use the client-provided GTK dialogs.
@@ -442,10 +571,20 @@ bool ClientHandler::OnChromeCommand(CefRefPtr<CefBrowser> browser,
CEF_REQUIRE_UI_THREAD();
DCHECK(MainContext::Get()->UseChromeRuntime());
if (!with_controls_ &&
(disposition != WOD_CURRENT_TAB || !IsAllowedCommandId(command_id))) {
// Block everything that doesn't target the current tab or isn't an
// allowed command ID.
const bool allowed = IsAllowedAppMenuCommandId(command_id) ||
IsAllowedContextMenuCommandId(command_id);
bool block = false;
if (filter_chrome_commands_) {
// Block all commands that aren't specifically allowed.
block = !allowed;
} else if (!with_controls_) {
// If controls are hidden, block all commands that don't target the current
// tab or aren't specifically allowed.
block = disposition != WOD_CURRENT_TAB || !allowed;
}
if (block) {
LOG(INFO) << "Blocking command " << command_id << " with disposition "
<< disposition;
return true;
@@ -455,6 +594,36 @@ bool ClientHandler::OnChromeCommand(CefRefPtr<CefBrowser> browser,
return false;
}
bool ClientHandler::IsChromeAppMenuItemVisible(CefRefPtr<CefBrowser> browser,
int command_id) {
CEF_REQUIRE_UI_THREAD();
DCHECK(MainContext::Get()->UseChromeRuntime());
if (!filter_chrome_commands_) {
return true;
}
return IsAllowedAppMenuCommandId(command_id);
}
bool ClientHandler::IsChromePageActionIconVisible(
cef_chrome_page_action_icon_type_t icon_type) {
CEF_REQUIRE_UI_THREAD();
DCHECK(MainContext::Get()->UseChromeRuntime());
if (!filter_chrome_commands_) {
return true;
}
return IsAllowedPageActionIcon(icon_type);
}
bool ClientHandler::IsChromeToolbarButtonVisible(
cef_chrome_toolbar_button_type_t button_type) {
CEF_REQUIRE_UI_THREAD();
DCHECK(MainContext::Get()->UseChromeRuntime());
if (!filter_chrome_commands_) {
return true;
}
return IsAllowedToolbarButton(button_type);
}
void ClientHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefContextMenuParams> params,
@@ -462,9 +631,9 @@ void ClientHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,
CEF_REQUIRE_UI_THREAD();
const bool use_chrome_runtime = MainContext::Get()->UseChromeRuntime();
if (use_chrome_runtime && !with_controls_) {
if (use_chrome_runtime && (!with_controls_ || filter_chrome_commands_)) {
// Remove all disallowed menu items.
FilterMenuModel(model);
FilterContextMenuModel(model);
}
if ((params->GetTypeFlags() & (CM_TYPEFLAG_PAGE | CM_TYPEFLAG_FRAME)) != 0) {
@@ -1438,61 +1607,4 @@ void ClientHandler::SetOfflineState(CefRefPtr<CefBrowser> browser,
/*message_id=*/0, "Network.emulateNetworkConditions", params);
}
void ClientHandler::FilterMenuModel(CefRefPtr<CefMenuModel> model) {
// Evaluate from the bottom to the top because we'll be removing menu items.
for (size_t x = model->GetCount(); x > 0; --x) {
const auto i = x - 1;
const auto type = model->GetTypeAt(i);
if (type == MENUITEMTYPE_SUBMENU) {
// Filter sub-menu and remove if empty.
auto sub_model = model->GetSubMenuAt(i);
FilterMenuModel(sub_model);
if (sub_model->GetCount() == 0) {
model->RemoveAt(i);
}
} else if (type == MENUITEMTYPE_SEPARATOR) {
// A separator shouldn't be the first or last element in the menu, and
// there shouldn't be multiple in a row.
if (i == 0 || i == model->GetCount() - 1 ||
model->GetTypeAt(i + 1) == MENUITEMTYPE_SEPARATOR) {
model->RemoveAt(i);
}
} else if (!IsAllowedCommandId(model->GetCommandIdAt(i))) {
model->RemoveAt(i);
}
}
}
bool ClientHandler::IsAllowedCommandId(int command_id) {
// Only the commands in this array will be allowed.
static const int kAllowedCommandIds[] = {
// Page navigation.
IDC_BACK,
IDC_FORWARD,
IDC_RELOAD,
IDC_RELOAD_BYPASSING_CACHE,
IDC_RELOAD_CLEARING_CACHE,
IDC_STOP,
// Printing.
IDC_PRINT,
// Edit controls.
IDC_CONTENT_CONTEXT_CUT,
IDC_CONTENT_CONTEXT_COPY,
IDC_CONTENT_CONTEXT_PASTE,
IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE,
IDC_CONTENT_CONTEXT_DELETE,
IDC_CONTENT_CONTEXT_SELECTALL,
IDC_CONTENT_CONTEXT_UNDO,
IDC_CONTENT_CONTEXT_REDO,
};
for (size_t i = 0; i < std::size(kAllowedCommandIds); ++i) {
if (command_id == kAllowedCommandIds[i]) {
return true;
}
}
return false;
}
} // namespace client