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