1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/frame_host/popup_menu_helper_mac.h" 6 7#import "base/mac/scoped_nsobject.h" 8#import "base/mac/scoped_sending_event.h" 9#include "base/message_loop/message_loop_current.h" 10#import "base/message_loop/message_pump_mac.h" 11#include "content/browser/frame_host/frame_tree.h" 12#include "content/browser/frame_host/frame_tree_node.h" 13#include "content/browser/frame_host/render_frame_host_impl.h" 14#include "content/browser/renderer_host/render_view_host_impl.h" 15#import "content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h" 16#include "content/browser/renderer_host/render_widget_host_view_mac.h" 17#include "content/browser/renderer_host/webmenurunner_mac.h" 18#include "content/common/buildflags.h" 19#import "ui/base/cocoa/base_view.h" 20 21namespace content { 22 23namespace { 24 25bool g_allow_showing_popup_menus = true; 26 27} // namespace 28 29PopupMenuHelper::PopupMenuHelper(Delegate* delegate, 30 RenderFrameHost* render_frame_host) 31 : delegate_(delegate), 32 render_frame_host_( 33 static_cast<RenderFrameHostImpl*>(render_frame_host)->GetWeakPtr()) { 34 RenderWidgetHost* widget_host = 35 render_frame_host->GetRenderViewHost()->GetWidget(); 36 observer_.Add(widget_host); 37} 38 39PopupMenuHelper::~PopupMenuHelper() { 40 Hide(); 41} 42 43void PopupMenuHelper::ShowPopupMenu( 44 const gfx::Rect& bounds, 45 int item_height, 46 double item_font_size, 47 int selected_item, 48 const std::vector<MenuItem>& items, 49 bool right_aligned, 50 bool allow_multiple_selection) { 51 // Only single selection list boxes show a popup on Mac. 52 DCHECK(!allow_multiple_selection); 53 54 if (!g_allow_showing_popup_menus) 55 return; 56 57#if BUILDFLAG(USE_EXTERNAL_POPUP_MENU) 58 // Retain the Cocoa view for the duration of the pop-up so that it can't be 59 // dealloced if my Destroy() method is called while the pop-up's up (which 60 // would in turn delete me, causing a crash once the -runMenuInView 61 // call returns. That's what was happening in <http://crbug.com/33250>). 62 RenderWidgetHostViewMac* rwhvm = 63 static_cast<RenderWidgetHostViewMac*>(GetRenderWidgetHostView()); 64 base::scoped_nsobject<RenderWidgetHostViewCocoa> cocoa_view( 65 [rwhvm->GetInProcessNSView() retain]); 66 67 // Display the menu. 68 base::scoped_nsobject<WebMenuRunner> runner([[WebMenuRunner alloc] 69 initWithItems:items 70 fontSize:item_font_size 71 rightAligned:right_aligned]); 72 73 // Take a weak reference so that Hide() can close the menu. 74 menu_runner_ = runner; 75 76 base::WeakPtr<PopupMenuHelper> weak_ptr(weak_ptr_factory_.GetWeakPtr()); 77 78 { 79 // Make sure events can be pumped while the menu is up. 80 base::MessageLoopCurrent::ScopedNestableTaskAllower allow; 81 82 // One of the events that could be pumped is |window.close()|. 83 // User-initiated event-tracking loops protect against this by 84 // setting flags in -[CrApplication sendEvent:], but since 85 // web-content menus are initiated by IPC message the setup has to 86 // be done manually. 87 base::mac::ScopedSendingEvent sending_event_scoper; 88 89 // Ensure the UI can update while the menu is fading out. 90 pump_in_fade_ = std::make_unique<base::ScopedPumpMessagesInPrivateModes>(); 91 92 // Now run a NESTED EVENT LOOP until the pop-up is finished. 93 [runner runMenuInView:cocoa_view 94 withBounds:[cocoa_view flipRectToNSRect:bounds] 95 initialIndex:selected_item]; 96 } 97 98 if (!weak_ptr) 99 return; // Handle |this| being deleted. 100 101 pump_in_fade_ = nullptr; 102 menu_runner_ = nil; 103 104 // The RenderFrameHost may be deleted while running the menu, or it may have 105 // requested the close. Don't notify in these cases. 106 if (render_frame_host_ && !popup_was_hidden_) { 107 if ([runner menuItemWasChosen]) 108 render_frame_host_->DidSelectPopupMenuItem([runner indexOfSelectedItem]); 109 else 110 render_frame_host_->DidCancelPopupMenu(); 111 } 112 113#endif 114 delegate_->OnMenuClosed(); // May delete |this|. 115} 116 117void PopupMenuHelper::Hide() { 118 // Blink core reuses the PopupMenu of an element and first invokes Hide() over 119 // IPC if a menu is already showing. Attempting to show a new menu while the 120 // old menu is fading out confuses AppKit, since we're still in the NESTED 121 // EVENT LOOP of ShowPopupMenu(). Disable pumping of events in the fade 122 // animation of the old menu in this case so that it closes synchronously. 123 // See http://crbug.com/812260. 124 pump_in_fade_ = nullptr; 125 126 if (menu_runner_) 127 [menu_runner_ hide]; 128 popup_was_hidden_ = true; 129} 130 131// static 132void PopupMenuHelper::DontShowPopupMenuForTesting() { 133 g_allow_showing_popup_menus = false; 134} 135 136RenderWidgetHostViewMac* PopupMenuHelper::GetRenderWidgetHostView() const { 137 return static_cast<RenderWidgetHostViewMac*>( 138 render_frame_host_->frame_tree_node() 139 ->frame_tree() 140 ->root() 141 ->current_frame_host() 142 ->GetView()); 143} 144 145void PopupMenuHelper::RenderWidgetHostVisibilityChanged( 146 RenderWidgetHost* widget_host, 147 bool became_visible) { 148 if (!became_visible) 149 Hide(); 150} 151 152void PopupMenuHelper::RenderWidgetHostDestroyed(RenderWidgetHost* widget_host) { 153 observer_.Remove(widget_host); 154} 155 156} // namespace content 157