[go: nahoru, domu]

blob: 3cc34eaacd32bdeff4bc149f96d72e56dc64c892 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/raw_ptr.h"
#import "chrome/browser/ui/views/frame/browser_frame_mac.h"
#import "base/apple/foundation_util.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/global_keyboard_shortcuts_mac.h"
#include "chrome/browser/media/router/media_router_feature.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"
#import "chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.h"
#include "chrome/browser/ui/views/frame/browser_frame.h"
#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_utils.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/dom_distiller/content/browser/distillable_page_utils.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#import "components/remote_cocoa/app_shim/window_touch_bar_delegate.h"
#include "components/remote_cocoa/common/application.mojom.h"
#include "components/remote_cocoa/common/native_widget_ns_window.mojom.h"
#include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
#include "components/web_modal/web_contents_modal_dialog_host.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#import "ui/base/cocoa/window_size_constants.h"
#include "ui/base/l10n/l10n_util.h"
#import "ui/views/cocoa/native_widget_mac_ns_window_host.h"
namespace {
AppShimHost* GetHostForBrowser(Browser* browser) {
auto* const shim_manager = apps::AppShimManager::Get();
if (!shim_manager)
return nullptr;
return shim_manager->GetHostForRemoteCocoaBrowser(browser);
}
bool UsesRemoteCocoaApplicationHost(Browser* browser) {
auto* const shim_manager = apps::AppShimManager::Get();
return shim_manager && shim_manager->BrowserUsesRemoteCocoa(browser);
}
bool ShouldHandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
// |event.skip_in_browser| 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_in_browser)
return false;
// Ignore synthesized keyboard events. See http://crbug.com/23221.
if (event.GetType() == content::NativeWebKeyboardEvent::Type::kChar)
return false;
// If the event was not synthesized it should have an os_event.
DCHECK(event.os_event);
// Do not fire shortcuts on key up.
return event.os_event.Get().type == NSEventTypeKeyDown;
}
} // namespace
// Bridge Obj-C class for WindowTouchBarDelegate and
// BrowserWindowTouchBarController.
@interface BrowserWindowTouchBarViewsDelegate
: NSObject <WindowTouchBarDelegate>
- (BrowserWindowTouchBarController*)touchBarController;
@end
@implementation BrowserWindowTouchBarViewsDelegate {
raw_ptr<Browser> _browser;
NSWindow* __weak _window;
BrowserWindowTouchBarController* __strong _touchBarController;
}
- (instancetype)initWithBrowser:(Browser*)browser window:(NSWindow*)window {
if ((self = [super init])) {
_browser = browser;
_window = window;
}
return self;
}
- (BrowserWindowTouchBarController*)touchBarController {
return _touchBarController;
}
- (NSTouchBar*)makeTouchBar {
if (!_touchBarController) {
_touchBarController =
[[BrowserWindowTouchBarController alloc] initWithBrowser:_browser
window:_window];
}
return [_touchBarController makeTouchBar];
}
@end
BrowserFrameMac::BrowserFrameMac(BrowserFrame* browser_frame,
BrowserView* browser_view)
: views::NativeWidgetMac(browser_frame), browser_view_(browser_view) {
if (GetRemoteCocoaApplicationHost()) {
// Only add observer on PWA.
chrome::AddCommandObserver(browser_view_->browser(), IDC_BACK, this);
chrome::AddCommandObserver(browser_view_->browser(), IDC_FORWARD, this);
}
}
BrowserFrameMac::~BrowserFrameMac() {
if (UsesRemoteCocoaApplicationHost(browser_view_->browser())) {
chrome::RemoveCommandObserver(browser_view_->browser(), IDC_BACK, this);
chrome::RemoveCommandObserver(browser_view_->browser(), IDC_FORWARD, this);
}
}
BrowserWindowTouchBarController* BrowserFrameMac::GetTouchBarController()
const {
return [touch_bar_delegate_ touchBarController];
}
////////////////////////////////////////////////////////////////////////////////
// BrowserFrameMac, views::NativeWidgetMac implementation:
int32_t BrowserFrameMac::SheetOffsetY() {
// ModalDialogHost::GetDialogPosition() is relative to the host view. In
// practice, this ends up being the widget's content view.
web_modal::WebContentsModalDialogHost* dialog_host =
browser_view_->GetWebContentsModalDialogHost();
return dialog_host->GetDialogPosition(gfx::Size()).y();
}
void BrowserFrameMac::GetWindowFrameTitlebarHeight(
bool* override_titlebar_height,
float* titlebar_height) {
if (browser_view_ && browser_view_->frame() &&
browser_view_->frame()->GetFrameView()) {
*override_titlebar_height = true;
*titlebar_height =
browser_view_->GetTabStripHeight() +
browser_view_->frame()->GetFrameView()->GetTopInset(true);
if (!browser_view_->GetTabStripVisible()) {
*titlebar_height +=
browser_view_->GetWebAppFrameToolbarPreferredSize().height() +
kWebAppMenuMargin * 2;
}
} else {
*override_titlebar_height = false;
*titlebar_height = 0;
}
}
void BrowserFrameMac::OnFocusWindowToolbar() {
chrome::ExecuteCommand(browser_view_->browser(), IDC_FOCUS_TOOLBAR);
}
void BrowserFrameMac::OnWindowFullscreenTransitionStart() {
browser_view_->FullscreenStateChanging();
}
void BrowserFrameMac::OnWindowFullscreenTransitionComplete() {
browser_view_->FullscreenStateChanged();
}
void BrowserFrameMac::ValidateUserInterfaceItem(
int32_t tag,
remote_cocoa::mojom::ValidateUserInterfaceItemResult* result) {
Browser* browser = browser_view_->browser();
if (!chrome::SupportsCommand(browser, tag)) {
result->enable = false;
return;
}
// Generate return value (enabled state).
result->enable = chrome::IsCommandEnabled(browser, tag);
switch (tag) {
case IDC_CLOSE_TAB:
// Disable "close tab" if the receiving window is not tabbed.
// We simply check whether the item has a keyboard shortcut set here;
// app_controller_mac.mm actually determines whether the item should
// be enabled.
result->disable_if_has_no_key_equivalent = true;
break;
case IDC_FULLSCREEN: {
result->new_title.emplace(l10n_util::GetStringUTF16(
browser->window()->IsFullscreen() ? IDS_EXIT_FULLSCREEN_MAC
: IDS_ENTER_FULLSCREEN_MAC));
break;
}
case IDC_SHOW_AS_TAB: {
// Hide this menu option if the window is tabbed or is the devtools
// window.
result->new_hidden_state =
browser->is_type_normal() || browser->is_type_devtools();
break;
}
case IDC_ROUTE_MEDIA: {
// Hide this menu option if Media Router is disabled.
result->new_hidden_state =
!media_router::MediaRouterEnabled(browser->profile());
break;
}
case IDC_DISTILL_PAGE: {
// Enable the reader mode option if the page is a distilled page
// or if the page is distillable.
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
absl::optional<dom_distiller::DistillabilityResult> distillability =
dom_distiller::GetLatestResult(web_contents);
bool distillable =
distillability && distillability.value().is_distillable;
bool is_distilled = dom_distiller::url_utils::IsDistilledPage(
web_contents->GetLastCommittedURL());
result->new_title.emplace(l10n_util::GetStringUTF16(
is_distilled ? IDS_EXIT_DISTILLED_PAGE : IDS_DISTILL_PAGE));
result->enable = distillable || is_distilled;
break;
}
default:
break;
}
// If the item is toggleable, find its toggle state and
// try to update it. This is a little awkward, but the alternative is
// to check after a commandDispatch, which seems worse.
// On Windows this logic happens in bookmark_bar_view.cc. This simply updates
// the menu item; it does not display the bookmark bar itself.
result->set_toggle_state = true;
switch (tag) {
default:
result->set_toggle_state = false;
break;
case IDC_SHOW_BOOKMARK_BAR: {
PrefService* prefs = browser->profile()->GetPrefs();
result->new_toggle_state =
prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar);
break;
}
case IDC_TOGGLE_FULLSCREEN_TOOLBAR: {
web_app::AppBrowserController* app_controller = browser->app_controller();
if (app_controller) {
result->new_toggle_state =
app_controller->AlwaysShowToolbarInFullscreen();
} else {
PrefService* prefs = browser->profile()->GetPrefs();
result->new_toggle_state =
prefs->GetBoolean(prefs::kShowFullscreenToolbar);
}
break;
}
case IDC_SHOW_FULL_URLS: {
PrefService* prefs = browser->profile()->GetPrefs();
result->new_toggle_state =
prefs->GetBoolean(omnibox::kPreventUrlElisionsInOmnibox);
// Disable this menu option if the show full URLs pref is managed.
result->enable =
!prefs->FindPreference(omnibox::kPreventUrlElisionsInOmnibox)
->IsManaged();
break;
}
case IDC_TOGGLE_JAVASCRIPT_APPLE_EVENTS: {
PrefService* prefs = browser->profile()->GetPrefs();
result->new_toggle_state =
prefs->GetBoolean(prefs::kAllowJavascriptAppleEvents);
break;
}
case IDC_WINDOW_MUTE_SITE: {
TabStripModel* model = browser->tab_strip_model();
// Menu items may be validated during browser startup, before the
// TabStripModel has been populated. Short-circuit to false in that case.
result->new_toggle_state =
!model->empty() && model->active_index() != TabStripModel::kNoTab &&
!model->WillContextMenuMuteSites(model->active_index());
break;
}
case IDC_WINDOW_PIN_TAB: {
TabStripModel* model = browser->tab_strip_model();
result->new_toggle_state =
!model->empty() && !model->WillContextMenuPin(model->active_index());
break;
}
case IDC_WINDOW_GROUP_TAB: {
TabStripModel* model = browser->tab_strip_model();
result->new_toggle_state =
!model->empty() &&
!model->WillContextMenuGroup(model->active_index());
break;
}
}
}
bool BrowserFrameMac::WillExecuteCommand(
int32_t command,
WindowOpenDisposition window_open_disposition,
bool is_before_first_responder) {
Browser* browser = browser_view_->browser();
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 (extensions::GlobalShortcutListener::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.
content::NativeWebKeyboardEvent dummy_event(
blink::WebInputEvent::Type::kKeyDown, 0, base::TimeTicks());
if (!browser->command_controller()->IsReservedCommandOrKey(command,
dummy_event)) {
return false;
}
}
return true;
}
bool BrowserFrameMac::ExecuteCommand(
int32_t command,
WindowOpenDisposition window_open_disposition,
bool is_before_first_responder) {
if (!WillExecuteCommand(command, window_open_disposition,
is_before_first_responder))
return false;
Browser* browser = browser_view_->browser();
chrome::ExecuteCommandWithDisposition(browser, command,
window_open_disposition);
return true;
}
void BrowserFrameMac::PopulateCreateWindowParams(
const views::Widget::InitParams& widget_params,
remote_cocoa::mojom::CreateWindowParams* params) {
params->style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskResizable;
if (browser_view_->GetIsNormalType() || browser_view_->GetIsWebAppType()) {
params->window_class = remote_cocoa::mojom::WindowClass::kBrowser;
params->style_mask |= NSWindowStyleMaskFullSizeContentView;
// Ensure tabstrip/profile button are visible.
params->titlebar_appears_transparent = true;
// Hosted apps draw their own window title.
if (browser_view_->GetIsWebAppType())
params->window_title_hidden = true;
} else if (browser_view_->GetIsPictureInPictureType()) {
params->window_class = remote_cocoa::mojom::WindowClass::kFrameless;
params->style_mask = NSWindowStyleMaskFullSizeContentView |
NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
} else {
params->window_class = remote_cocoa::mojom::WindowClass::kDefault;
}
params->animation_enabled = true;
}
NativeWidgetMacNSWindow* BrowserFrameMac::CreateNSWindow(
const remote_cocoa::mojom::CreateWindowParams* params) {
NativeWidgetMacNSWindow* ns_window = NativeWidgetMac::CreateNSWindow(params);
touch_bar_delegate_ = [[BrowserWindowTouchBarViewsDelegate alloc]
initWithBrowser:browser_view_->browser()
window:ns_window];
[ns_window setWindowTouchBarDelegate:touch_bar_delegate_];
return ns_window;
}
remote_cocoa::ApplicationHost*
BrowserFrameMac::GetRemoteCocoaApplicationHost() {
if (auto* host = GetHostForBrowser(browser_view_->browser()))
return host->GetRemoteCocoaApplicationHost();
return nullptr;
}
void BrowserFrameMac::OnWindowInitialized() {
if (auto* bridge = GetInProcessNSWindowBridge()) {
bridge->SetCommandDispatcher([[ChromeCommandDispatcherDelegate alloc] init],
[[BrowserWindowCommandHandler alloc] init]);
} else {
if (auto* host = GetHostForBrowser(browser_view_->browser())) {
host->GetAppShim()->CreateCommandDispatcherForWidget(
GetNSWindowHost()->bridged_native_widget_id());
}
}
}
void BrowserFrameMac::OnWindowDestroying(gfx::NativeWindow native_window) {
// Clear delegates set in CreateNSWindow() to prevent objects with a reference
// to |window| attempting to validate commands by looking for a Browser*.
NativeWidgetMacNSWindow* ns_window =
base::apple::ObjCCastStrict<NativeWidgetMacNSWindow>(
native_window.GetNativeNSWindow());
[ns_window setWindowTouchBarDelegate:nil];
}
int BrowserFrameMac::GetMinimizeButtonOffset() const {
NOTIMPLEMENTED();
return 0;
}
void BrowserFrameMac::EnabledStateChangedForCommand(int id, bool enabled) {
switch (id) {
case IDC_BACK:
GetNSWindowHost()->CanGoBack(enabled);
break;
case IDC_FORWARD:
GetNSWindowHost()->CanGoForward(enabled);
break;
default:
NOTREACHED_NORETURN();
}
}
////////////////////////////////////////////////////////////////////////////////
// BrowserFrameMac, NativeBrowserFrame implementation:
views::Widget::InitParams BrowserFrameMac::GetWidgetParams() {
views::Widget::InitParams params;
params.native_widget = this;
return params;
}
bool BrowserFrameMac::UseCustomFrame() const {
return browser_view_->GetIsPictureInPictureType();
}
bool BrowserFrameMac::UsesNativeSystemMenu() const {
return false;
}
bool BrowserFrameMac::ShouldSaveWindowPlacement() const {
return true;
}
void BrowserFrameMac::GetWindowPlacement(
gfx::Rect* bounds,
ui::WindowShowState* show_state) const {
return NativeWidgetMac::GetWindowPlacement(bounds, show_state);
}
content::KeyboardEventProcessingResult BrowserFrameMac::PreHandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
// On macOS, all keyEquivalents that use modifier keys are handled by
// -[CommandDispatcher performKeyEquivalent:]. If this logic is being hit,
// it means that the event was not handled, so we must return either
// NOT_HANDLED or NOT_HANDLED_IS_SHORTCUT.
NSEvent* ns_event = event.os_event.Get();
if (EventUsesPerformKeyEquivalent(ns_event)) {
int command_id = CommandForKeyEvent(ns_event).chrome_command;
if (command_id == -1) {
command_id = DelayedWebContentsCommandForKeyEvent(ns_event);
}
if (command_id != -1) {
return content::KeyboardEventProcessingResult::NOT_HANDLED_IS_SHORTCUT;
}
}
return content::KeyboardEventProcessingResult::NOT_HANDLED;
}
bool BrowserFrameMac::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
if (!ShouldHandleKeyboardEvent(event)) {
return false;
}
// Redispatch the event. If it's a keyEquivalent:, this gives
// CommandDispatcher the opportunity to finish passing the event to consumers.
return GetNSWindowHost()->RedispatchKeyEvent(event.os_event.Get());
}
bool BrowserFrameMac::ShouldRestorePreviousBrowserWidgetState() const {
return true;
}
bool BrowserFrameMac::ShouldUseInitialVisibleOnAllWorkspaces() const {
return true;
}
void BrowserFrameMac::AnnounceTextInInProcessWindow(
const std::u16string& text) {
NSAccessibilityPriorityLevel priority = NSAccessibilityPriorityHigh;
NSDictionary* notification_info = @{
NSAccessibilityAnnouncementKey : base::SysUTF16ToNSString(text),
NSAccessibilityPriorityKey : @(priority)
};
NSWindow* ns_window = GetNSWindowHost()->GetInProcessNSWindow();
if (ns_window) {
NSAccessibilityPostNotificationWithUserInfo(
ns_window, NSAccessibilityAnnouncementRequestedNotification,
notification_info);
}
}