extension_popup.cc revision 010d83a9304c5a91596085d917d248abff47903a
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 "chrome/browser/ui/views/extensions/extension_popup.h" 6 7#include "base/bind.h" 8#include "base/message_loop/message_loop.h" 9#include "chrome/browser/chrome_notification_types.h" 10#include "chrome/browser/devtools/devtools_window.h" 11#include "chrome/browser/extensions/extension_view_host.h" 12#include "chrome/browser/extensions/extension_view_host_factory.h" 13#include "chrome/browser/platform_util.h" 14#include "chrome/browser/profiles/profile.h" 15#include "chrome/browser/ui/browser.h" 16#include "chrome/browser/ui/browser_window.h" 17#include "chrome/browser/ui/host_desktop.h" 18#include "chrome/browser/ui/tabs/tab_strip_model.h" 19#include "chrome/browser/ui/views/frame/browser_view.h" 20#include "content/public/browser/devtools_agent_host.h" 21#include "content/public/browser/devtools_manager.h" 22#include "content/public/browser/notification_details.h" 23#include "content/public/browser/notification_source.h" 24#include "content/public/browser/render_view_host.h" 25#include "content/public/browser/web_contents.h" 26#include "ui/gfx/insets.h" 27#include "ui/views/layout/fill_layout.h" 28#include "ui/views/widget/widget.h" 29 30#if defined(USE_AURA) 31#include "ui/aura/window.h" 32#include "ui/wm/core/window_animations.h" 33#include "ui/wm/core/window_util.h" 34#include "ui/wm/public/activation_client.h" 35#endif 36 37#if defined(OS_WIN) 38#include "ui/views/win/hwnd_util.h" 39#endif 40 41using content::BrowserContext; 42using content::RenderViewHost; 43using content::WebContents; 44 45namespace { 46 47// Returns true if |possible_owner| is the owner of |child|. 48bool IsOwnerOf(gfx::NativeView child, gfx::NativeView possible_owner) { 49 if (!child) 50 return false; 51#if defined(OS_WIN) 52 if (::GetWindow(views::HWNDForNativeView(child), GW_OWNER) == 53 views::HWNDForNativeView(possible_owner)) 54 return true; 55#endif 56 return false; 57} 58 59} // namespace 60 61// The minimum/maximum dimensions of the popup. 62// The minimum is just a little larger than the size of the button itself. 63// The maximum is an arbitrary number that should be smaller than most screens. 64const int ExtensionPopup::kMinWidth = 25; 65const int ExtensionPopup::kMinHeight = 25; 66const int ExtensionPopup::kMaxWidth = 800; 67const int ExtensionPopup::kMaxHeight = 600; 68 69ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host, 70 views::View* anchor_view, 71 views::BubbleBorder::Arrow arrow, 72 ShowAction show_action) 73 : BubbleDelegateView(anchor_view, arrow), 74 host_(host), 75 devtools_callback_(base::Bind( 76 &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))) { 77 inspect_with_devtools_ = show_action == SHOW_AND_INSPECT; 78 // Adjust the margin so that contents fit better. 79 const int margin = views::BubbleBorder::GetCornerRadius() / 2; 80 set_margins(gfx::Insets(margin, margin, margin, margin)); 81 SetLayoutManager(new views::FillLayout()); 82 AddChildView(host->view()); 83 host->view()->set_container(this); 84 // Use OnNativeFocusChange to check for child window activation on deactivate. 85 set_close_on_deactivate(false); 86 87 // Wait to show the popup until the contained host finishes loading. 88 registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 89 content::Source<WebContents>(host->host_contents())); 90 91 // Listen for the containing view calling window.close(); 92 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 93 content::Source<BrowserContext>(host->browser_context())); 94 content::DevToolsManager::GetInstance()->AddAgentStateCallback( 95 devtools_callback_); 96 97 host_->view()->browser()->tab_strip_model()->AddObserver(this); 98} 99 100ExtensionPopup::~ExtensionPopup() { 101 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( 102 devtools_callback_); 103 104 host_->view()->browser()->tab_strip_model()->RemoveObserver(this); 105} 106 107void ExtensionPopup::Observe(int type, 108 const content::NotificationSource& source, 109 const content::NotificationDetails& details) { 110 switch (type) { 111 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: 112 DCHECK(content::Source<WebContents>(host()->host_contents()) == source); 113 // Show when the content finishes loading and its width is computed. 114 ShowBubble(); 115 break; 116 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: 117 // If we aren't the host of the popup, then disregard the notification. 118 if (content::Details<extensions::ExtensionHost>(host()) == details) 119 GetWidget()->Close(); 120 break; 121 default: 122 NOTREACHED() << L"Received unexpected notification"; 123 } 124} 125 126void ExtensionPopup::OnDevToolsStateChanged( 127 content::DevToolsAgentHost* agent_host, bool attached) { 128 // First check that the devtools are being opened on this popup. 129 if (host()->render_view_host() != agent_host->GetRenderViewHost()) 130 return; 131 132 if (attached) { 133 // Set inspect_with_devtools_ so the popup will be kept open while 134 // the devtools are open. 135 inspect_with_devtools_ = true; 136 } else { 137 // Widget::Close posts a task, which should give the devtools window a 138 // chance to finish detaching from the inspected RenderViewHost. 139 GetWidget()->Close(); 140 } 141} 142 143void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) { 144 SizeToContents(); 145} 146 147gfx::Size ExtensionPopup::GetPreferredSize() { 148 // Constrain the size to popup min/max. 149 gfx::Size sz = views::View::GetPreferredSize(); 150 sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width()))); 151 sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height()))); 152 return sz; 153} 154 155void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) { 156 BubbleDelegateView::OnWidgetDestroying(widget); 157#if defined(USE_AURA) 158 aura::Window* bubble_window = GetWidget()->GetNativeWindow(); 159 aura::client::ActivationClient* activation_client = 160 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 161 activation_client->RemoveObserver(this); 162#endif 163} 164 165void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget, 166 bool active) { 167 // Dismiss only if the window being activated is not owned by this popup's 168 // window. In particular, don't dismiss when we lose activation to a child 169 // dialog box. Possibly relevant: http://crbug.com/106723 and 170 // http://crbug.com/179786 171 views::Widget* this_widget = GetWidget(); 172 173 // TODO(msw): Resolve crashes and remove checks. See: http://crbug.com/327776 174 CHECK(!close_on_deactivate()); 175 CHECK(this_widget); 176 CHECK(widget); 177 178 gfx::NativeView activated_view = widget->GetNativeView(); 179 gfx::NativeView this_view = this_widget->GetNativeView(); 180 if (active && !inspect_with_devtools_ && activated_view != this_view && 181 !IsOwnerOf(activated_view, this_view)) 182 this_widget->Close(); 183} 184 185#if defined(USE_AURA) 186void ExtensionPopup::OnWindowActivated(aura::Window* gained_active, 187 aura::Window* lost_active) { 188 // DesktopNativeWidgetAura does not trigger the expected browser widget 189 // [de]activation events when activating widgets in its own root window. 190 // This additional check handles those cases. See: http://crbug.com/320889 191 aura::Window* this_window = GetWidget()->GetNativeWindow(); 192 aura::Window* anchor_window = anchor_widget()->GetNativeWindow(); 193 chrome::HostDesktopType host_desktop_type = 194 chrome::GetHostDesktopTypeForNativeWindow(this_window); 195 if (!inspect_with_devtools_ && anchor_window == gained_active && 196 host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH && 197 this_window->GetRootWindow() == anchor_window->GetRootWindow() && 198 wm::GetTransientParent(gained_active) != this_window) 199 GetWidget()->Close(); 200} 201#endif 202 203void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents, 204 content::WebContents* new_contents, 205 int index, 206 int reason) { 207 GetWidget()->Close(); 208} 209 210// static 211ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url, 212 Browser* browser, 213 views::View* anchor_view, 214 views::BubbleBorder::Arrow arrow, 215 ShowAction show_action) { 216 extensions::ExtensionViewHost* host = 217 extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser); 218 ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow, 219 show_action); 220 views::BubbleDelegateView::CreateBubble(popup); 221 222#if defined(USE_AURA) 223 gfx::NativeView native_view = popup->GetWidget()->GetNativeView(); 224 wm::SetWindowVisibilityAnimationType( 225 native_view, 226 wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); 227 wm::SetWindowVisibilityAnimationVerticalPosition( 228 native_view, 229 -3.0f); 230#endif 231 232 // If the host had somehow finished loading, then we'd miss the notification 233 // and not show. This seems to happen in single-process mode. 234 if (host->did_stop_loading()) 235 popup->ShowBubble(); 236 237#if defined(USE_AURA) 238 aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow(); 239 aura::client::ActivationClient* activation_client = 240 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 241 activation_client->AddObserver(popup); 242#endif 243 244 return popup; 245} 246 247void ExtensionPopup::ShowBubble() { 248 GetWidget()->Show(); 249 250 // Focus on the host contents when the bubble is first shown. 251 host()->host_contents()->Focus(); 252 253 if (inspect_with_devtools_) { 254 DevToolsWindow::OpenDevToolsWindow(host()->render_view_host(), 255 DevToolsToggleAction::ShowConsole()); 256 } 257} 258