1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui_browser/webui_browser_window.h"
#import <AppKit/AppKit.h>
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/apps/app_shim/app_shim_host_mac.h"
#include "chrome/browser/apps/app_shim/app_shim_manager_mac.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_commands.h"
#import "chrome/browser/ui/cocoa/browser_window_command_handler.h"
#import "chrome/browser/ui/cocoa/chrome_command_dispatcher_delegate.h"
#include "components/input/native_web_keyboard_event.h"
#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#include "ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.h"
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
#include "ui/views/widget/native_widget_mac.h"
#if !BUILDFLAG(IS_MAC)
#error "This file is macOS-only and should not be included on other platforms."
#endif
namespace {
AppShimHost* GetHostForBrowser(Browser* browser) {
auto* const shim_manager = apps::AppShimManager::Get();
if (!shim_manager) {
return nullptr;
}
return shim_manager->GetHostForRemoteCocoaBrowser(browser);
}
bool ShouldHandleKeyboardEvent(const input::NativeWebKeyboardEvent& event) {
// |event.skip_if_unhandled| is true when it shouldn't be handled by the
// browser if it was ignored by the renderer. See http://crbug.com/25000.
if (event.skip_if_unhandled) {
return false;
}
// Ignore synthesized keyboard events. See http://crbug.com/23221.
if (event.GetType() == input::NativeWebKeyboardEvent::Type::kChar) {
return false;
}
// Do not fire shortcuts on key up, and only forward shortcuts if we have an
// underlying os event.
return event.os_event && event.os_event.Get().type == NSEventTypeKeyDown;
}
} // namespace
bool WebUIBrowserWindow::HandleKeyboardEvent(
const input::NativeWebKeyboardEvent& event) {
if (!ShouldHandleKeyboardEvent(event)) {
return false;
}
// On Mac keyboard shortcuts are caught by the menu entries, so the keyboard
// shortcut events must be redispatched to the system so properly configured
// menu items can catch the keyboard shortcuts.
return views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
GetNativeWindow())
->RedispatchKeyEvent(event.os_event.Get());
}
// Note that the logic here is often derived from BrowserFrameMac.
class WebUIBrowserNativeWidgetMac : public views::NativeWidgetMac {
public:
WebUIBrowserNativeWidgetMac(Browser* browser, views::Widget* widget)
: NativeWidgetMac(widget), browser_(browser) {}
private:
// views::NativeWidgetMac implementation:
void ValidateUserInterfaceItem(
int32_t command,
remote_cocoa::mojom::ValidateUserInterfaceItemResult* result) override {
// This allows menu items like "Close Tabs" to be enabled when the browser
// has open tabs, which in turn enables the "Close Tabs" keyboard shortcut.
result->enable = chrome::IsCommandEnabled(browser_, command);
}
// For our ValidateUserInterfaceItem() to be called we must register the
// CommandDispatcher.
void OnWindowInitialized() override {
if (auto* bridge = GetInProcessNSWindowBridge()) {
bridge->SetCommandDispatcher(
[[ChromeCommandDispatcherDelegate alloc] init],
[[BrowserWindowCommandHandler alloc] init]);
} else {
if (auto* host = GetHostForBrowser(browser_)) {
host->GetAppShim()->CreateCommandDispatcherForWidget(
GetNSWindowHost()->bridged_native_widget_id());
}
}
}
bool WillExecuteCommand(int32_t command,
WindowOpenDisposition window_open_disposition,
bool is_before_first_responder) override {
if (is_before_first_responder) {
// The specification for this private extensions API is incredibly vague.
// For now, we avoid triggering chrome commands prior to giving the
// firstResponder a chance to handle the event.
if (ui::GlobalAcceleratorListener::GetInstance() &&
ui::GlobalAcceleratorListener::GetInstance()
->IsShortcutHandlingSuspended()) {
return false;
}
// If a command is reserved, then we also have it bypass the main menu.
// This is based on the rough approximation that reserved commands are
// also the ones that we want to be quickly repeatable.
// https://crbug.com/836947.
// The function IsReservedCommandOrKey does not examine its event argument
// on macOS.
input::NativeWebKeyboardEvent dummy_event(
blink::WebInputEvent::Type::kKeyDown, 0, base::TimeTicks());
if (!browser_->command_controller()->IsReservedCommandOrKey(
command, dummy_event)) {
return false;
}
}
return true;
}
bool ExecuteCommand(int32_t command,
WindowOpenDisposition window_open_disposition,
bool is_before_first_responder) override {
if (!WillExecuteCommand(command, window_open_disposition,
is_before_first_responder)) {
return false;
}
// Actually Execute the specific command with |browser_|.
// This facilitates commands initiated from keyboard shortcuts.
chrome::ExecuteCommandWithDisposition(browser_, command,
window_open_disposition);
return true;
}
// views::NativeWidget implementation:
views::internal::NativeWidgetPrivate* AsNativeWidgetPrivate() override {
return this;
}
raw_ptr<Browser> browser_;
};
views::NativeWidget* WebUIBrowserWindow::CreateNativeWidget() {
return new WebUIBrowserNativeWidgetMac(browser_.get(), widget_.get());
}
|