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#import <Carbon/Carbon.h> 6 7#include "content/browser/frame_host/popup_menu_helper_mac.h" 8 9#include "base/mac/scoped_nsobject.h" 10#import "base/mac/scoped_sending_event.h" 11#include "base/message_loop/message_loop.h" 12#include "content/browser/frame_host/render_frame_host_impl.h" 13#include "content/browser/renderer_host/render_view_host_impl.h" 14#include "content/browser/renderer_host/render_widget_host_view_mac.h" 15#include "content/browser/renderer_host/webmenurunner_mac.h" 16#include "content/public/browser/notification_source.h" 17#include "content/public/browser/notification_types.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(RenderFrameHost* render_frame_host) 29 : render_frame_host_(static_cast<RenderFrameHostImpl*>(render_frame_host)), 30 menu_runner_(nil), 31 popup_was_hidden_(false) { 32 notification_registrar_.Add( 33 this, NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, 34 Source<RenderWidgetHost>(render_frame_host->GetRenderViewHost())); 35} 36 37void PopupMenuHelper::ShowPopupMenu( 38 const gfx::Rect& bounds, 39 int item_height, 40 double item_font_size, 41 int selected_item, 42 const std::vector<MenuItem>& items, 43 bool right_aligned, 44 bool allow_multiple_selection) { 45 // Only single selection list boxes show a popup on Mac. 46 DCHECK(!allow_multiple_selection); 47 48 if (!g_allow_showing_popup_menus) 49 return; 50 51 // Retain the Cocoa view for the duration of the pop-up so that it can't be 52 // dealloced if my Destroy() method is called while the pop-up's up (which 53 // would in turn delete me, causing a crash once the -runMenuInView 54 // call returns. That's what was happening in <http://crbug.com/33250>). 55 RenderWidgetHostViewMac* rwhvm = 56 static_cast<RenderWidgetHostViewMac*>(GetRenderWidgetHostView()); 57 base::scoped_nsobject<RenderWidgetHostViewCocoa> cocoa_view( 58 [rwhvm->cocoa_view() retain]); 59 60 // Display the menu. 61 menu_runner_ = [[WebMenuRunner alloc] initWithItems:items 62 fontSize:item_font_size 63 rightAligned:right_aligned]; 64 65 { 66 // Make sure events can be pumped while the menu is up. 67 base::MessageLoop::ScopedNestableTaskAllower allow( 68 base::MessageLoop::current()); 69 70 // One of the events that could be pumped is |window.close()|. 71 // User-initiated event-tracking loops protect against this by 72 // setting flags in -[CrApplication sendEvent:], but since 73 // web-content menus are initiated by IPC message the setup has to 74 // be done manually. 75 base::mac::ScopedSendingEvent sending_event_scoper; 76 77 // Now run a SYNCHRONOUS NESTED EVENT LOOP until the pop-up is finished. 78 [menu_runner_ runMenuInView:cocoa_view 79 withBounds:[cocoa_view flipRectToNSRect:bounds] 80 initialIndex:selected_item]; 81 } 82 83 if (!render_frame_host_) { 84 // Bad news, the RenderFrameHost got deleted while we were off running the 85 // menu. Nothing to do. 86 [menu_runner_ release]; 87 menu_runner_ = nil; 88 return; 89 } 90 91 if (!popup_was_hidden_) { 92 if ([menu_runner_ menuItemWasChosen]) { 93 render_frame_host_->DidSelectPopupMenuItem( 94 [menu_runner_ indexOfSelectedItem]); 95 } else { 96 render_frame_host_->DidCancelPopupMenu(); 97 } 98 } 99 [menu_runner_ release]; 100 menu_runner_ = nil; 101} 102 103void PopupMenuHelper::Hide() { 104 if (menu_runner_) 105 [menu_runner_ hide]; 106 popup_was_hidden_ = true; 107} 108 109// static 110void PopupMenuHelper::DontShowPopupMenuForTesting() { 111 g_allow_showing_popup_menus = false; 112} 113 114RenderWidgetHostViewMac* PopupMenuHelper::GetRenderWidgetHostView() const { 115 return static_cast<RenderWidgetHostViewMac*>( 116 render_frame_host_->GetRenderViewHost()->GetView()); 117} 118 119void PopupMenuHelper::Observe(int type, 120 const NotificationSource& source, 121 const NotificationDetails& details) { 122 DCHECK(type == NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED); 123 DCHECK_EQ(Source<RenderWidgetHost>(source).ptr(), 124 render_frame_host_->GetRenderViewHost()); 125 render_frame_host_ = NULL; 126} 127 128} // namespace content 129